【问题标题】:Tail recursion in gcc/g++gcc/g++ 中的尾递归
【发布时间】:2013-03-31 14:50:13
【问题描述】:

我试图搜索,但无法找到:函数需要什么才能使 gcc 优化尾递归?是否有包含最重要案例的参考资料或清单?由于这是特定于版本的,我感兴趣的是 4.6.3 或更高版本(越新越好)。然而,事实上,任何具体的参考将不胜感激。

提前致谢!

【问题讨论】:

    标签: gcc recursion compiler-optimization tail-recursion


    【解决方案1】:

    启用-O2 后,gcc 将尽可能执行尾调用优化。现在的问题是:什么时候可能?

    只要递归调用发生在(单个)return 语句之前或内部,就可以消除单个递归调用,因此除了可能返回之外,没有可观察到的副作用不同的价值。这包括返回涉及三元运算符的表达式。
    请注意,即使在返回表达式中有几个递归调用(例如在著名的斐波那契示例中:return (n==1) ? 1 : fib(n-1)+fib(n-2);),仍然会应用尾调用递归,但只会应用到最后一个递归打电话。

    作为一个明显的特殊情况,如果编译器可以证明递归函数(及其参数)符合常量表达式的条件,它可以(达到可配置的最大递归深度,默认为 500)不仅消除尾调用,但函数的整个执行。这是 GCC 在-O2 经常且非常可靠地执行的操作,它甚至包括对某些库函数的调用,例如文字上的strlen

    【讨论】:

    • 我记得在几个版本之前我遇到了一个问题,即 gcc 没有优化某些虚函数的尾调用,但我现在无法重现这种行为。那里有什么特别的限制吗?
    • 如果对象类型在编译时已知,则虚拟函数调用将转换为“正常”函数调用,因此它们可以进行尾调用优化和内联。然而,当它们是真正的virtual函数调用时,它们不能被优化,因为在编译时无法知道将调用什么。除了通过 vtable 进行“真正的”虚拟调用之外,别无选择。例如,当您将B* 传递给您的函数并在该指针上调用foo()D1D2 派生自B)时,这可能会调用D1::fooD2::foo——递归与否,编译器无法知道。
    • 对于尾调用优化,您不需要知道函数是否递归,只需将 call 切换到 jmp 即可(或多或少)。 Gcc 可以用函数指针来做,你知道为什么 vtable 会有所不同吗(现在它也对虚函数进行尾调用)?
    • 以一种非常简单的方式,你是正确的,尾调用优化“仅”将 call 替换为 jmp(不过,如上所述,对可观察到的效果有额外的限制,并且稍微不同的代码位置)。但是,非静态调度比简单的call 或一般情况下的函数指针(在最简单 的情况下,它等同于函数指针)涉及更多一点。它当然可以(始终)可靠地使用虚拟函数的静态分派,并且它可能(或可能不)适用于动态分派。为什么或为什么不在一个特定的情况下很难说。
    • 在虚函数中,编译器应该知道递归调用要做什么。如果虚拟方法fooreturn foo() 结尾,那么它肯定可以替换为jmp 吗?它是递归的,因此无论虚拟如何,它都会知道要调用哪个foo。我能想到的唯一例外是,如果有人明确调用d -> Base::foo(),其中dDerived 类型的指针。然后第一个调用转到Base::foo(),递归调用转到Derived::foo()。它是否正确?这是为什么virtual很难的唯一原因(但一个很好的理由!)?
    猜你喜欢
    • 2011-05-28
    • 1970-01-01
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 2018-03-17
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    相关资源
    最近更新 更多