如果你编译gcc -O3 -fdump-tree-all
,您可以看到第一个将递归变成循环的转储是foo.c.035t.tailr1
。这意味着处理其他尾部调用的相同优化也可以处理这种稍微扩展的情况。递归形式为n * foo(...)
or n + foo(...)
手动处理并不难(见下文),并且由于可以准确描述如何处理,因此编译器可以自动执行该优化。
的优化main
更简单:内联可以将其变成10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 * 1
,如果乘法的所有操作数都是常量,则可以在编译时执行乘法。
Update:以下是如何手动删除递归的方法foo
,这可以自动完成。我并不是说这是 GCC 使用的方法,但这是一种现实的可能性。
首先,创建一个辅助函数。它的行为完全一样foo(n)
,只不过它的结果乘以一个额外的参数f
.
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
if (n == 0) return f * 1;
return f * n * foo(n-1);
}
然后,依次递归调用foo
递归调用foo_helper
,并依靠因子参数来摆脱乘法。
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
if (n == 0) return f;
return foo_helper(n-1, f * n);
}
把它变成一个循环:
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
restart:
if (n == 0) return f;
{
int newn = n-1;
int newf = f * n;
n = newn;
f = newf;
goto restart;
}
}
最后,内联foo_helper
:
int foo(int n)
{
int f = 1;
restart:
if (n == 0) return f;
{
int newn = n-1;
int newf = f * n;
n = newn;
f = newf;
goto restart;
}
}
(当然,这不是手动编写函数的最明智的方法。)