【发布时间】:2013-03-31 14:50:13
【问题描述】:
我试图搜索,但无法找到:函数需要什么才能使 gcc 优化尾递归?是否有包含最重要案例的参考资料或清单?由于这是特定于版本的,我感兴趣的是 4.6.3 或更高版本(越新越好)。然而,事实上,任何具体的参考将不胜感激。
提前致谢!
【问题讨论】:
标签: gcc recursion compiler-optimization tail-recursion
我试图搜索,但无法找到:函数需要什么才能使 gcc 优化尾递归?是否有包含最重要案例的参考资料或清单?由于这是特定于版本的,我感兴趣的是 4.6.3 或更高版本(越新越好)。然而,事实上,任何具体的参考将不胜感激。
提前致谢!
【问题讨论】:
标签: gcc recursion compiler-optimization tail-recursion
启用-O2 后,gcc 将尽可能执行尾调用优化。现在的问题是:什么时候可能?
只要递归调用发生在(单个)return 语句之前或内部,就可以消除单个递归调用,因此除了可能返回之外,没有可观察到的副作用不同的价值。这包括返回涉及三元运算符的表达式。
请注意,即使在返回表达式中有几个递归调用(例如在著名的斐波那契示例中:return (n==1) ? 1 : fib(n-1)+fib(n-2);),仍然会应用尾调用递归,但只会应用到最后一个递归打电话。
作为一个明显的特殊情况,如果编译器可以证明递归函数(及其参数)符合常量表达式的条件,它可以(达到可配置的最大递归深度,默认为 500)不仅消除尾调用,但函数的整个执行。这是 GCC 在-O2 经常且非常可靠地执行的操作,它甚至包括对某些库函数的调用,例如文字上的strlen。
【讨论】:
B* 传递给您的函数并在该指针上调用foo()(D1 和D2 派生自B)时,这可能会调用D1::foo 或D2::foo——递归与否,编译器无法知道。
call 切换到 jmp 即可(或多或少)。 Gcc 可以用函数指针来做,你知道为什么 vtable 会有所不同吗(现在它也对虚函数进行尾调用)?
call 替换为 jmp(不过,如上所述,对可观察到的效果有额外的限制,并且稍微不同的代码位置)。但是,非静态调度比简单的call 或一般情况下的函数指针(在最简单 的情况下,它等同于函数指针)涉及更多一点。它当然可以(始终)可靠地使用虚拟函数的静态分派,并且它可能(或可能不)适用于动态分派。为什么或为什么不在一个特定的情况下很难说。
foo 以return foo() 结尾,那么它肯定可以替换为jmp 吗?它是递归的,因此无论虚拟如何,它都会知道要调用哪个foo。我能想到的唯一例外是,如果有人明确调用d -> Base::foo(),其中d 是Derived 类型的指针。然后第一个调用转到Base::foo(),递归调用转到Derived::foo()。它是否正确?这是为什么virtual很难的唯一原因(但一个很好的理由!)?