哪个(如果有的话)C ++编译器会进行尾递归优化?

时间:2008-08-29 07:35:48

标签: c++ optimization tail-recursion

在我看来,在C和C ++中进行尾递归优化会很有效,但在调试时我似乎永远不会看到表示此优化的帧堆栈。这有点好,因为堆栈告诉我递归的深度。但是,优化也会很好。

是否有任何C ++编译器进行此优化?为什么?为什么不呢?

如何告诉编译器这样做?

  • 对于MSVC:/O2/Ox
  • 对于海湾合作委员会:-O2-O3

在某种情况下检查编译器是否已完成此操作如何?

  • 对于MSVC,启用PDB输出以跟踪代码,然后检查代码
  • GCC ..?

我仍然会考虑如何确定编译器是否对某个函数进行了如此优化(即使我发现Konrad告诉我这样做也让人放心)

总是可以通过进行无限递归并检查它是否导致无限循环或堆栈溢出来检查编译器是否完成此操作(我使用GCC执行此操作并发现-O2是但是我希望能够检查一下我知道会终止的某个功能。我很想有一个简单的方法来检查这个:)


经过一些测试,我发现析构函数破坏了进行优化的可能性。有时可能值得更改某些变量和临时值的范围,以确保它们在return语句开始之前超出范围。

如果在尾调用后需要运行任何析构函数,则无法进行尾调用优化。

5 个答案:

答案 0 :(得分:118)

所有当前的主流编译器都能很好地执行尾调优化(已经完成了十多年),even for mutually recursive calls如:

int bar(int, int);

int foo(int n, int acc) {
    return (n == 0) ? acc : bar(n - 1, acc + 2);
}

int bar(int n, int acc) {
    return (n == 0) ? acc : foo(n - 1, acc + 1);
}

让编译器进行优化非常简单:只需打开优化速度:

  • 对于MSVC,请使用/O2/Ox
  • 对于GCC,Clang和ICC,请使用-O3

检查编译器是否进行优化的一种简单方法是执行一次调用,否则会导致堆栈溢出 - 或者查看汇编输出。

作为一个有趣的历史记录,C的尾调优化被Mark Probst在diploma thesis的过程中添加到GCC中。论文描述了实施中的一些有趣的警告。值得一读。

答案 1 :(得分:21)

gcc 4.3.2将此功能(蹩脚/琐碎的atoi()实现)完全内联到main()。优化级别为-O1。我注意到如果我玩它(甚至将它从static改为extern,尾递归很快就消失了,所以我不会依赖它来获得程序的正确性。

#include <stdio.h>
static int atoi(const char *str, int n)
{
    if (str == 0 || *str == 0)
        return n;
    return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i)
        printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
    return 0;
}

答案 2 :(得分:12)

除了显而易见的(编译器不进行这种优化,除非你要求它),C ++中的尾调用优化很复杂:析构函数。

给出类似的东西:

/text()

编译器不能(通常)尾调用优化它,因为它需要 在递归调用返回后调用 int fn(int j, int i) { if (i <= 0) return j; Funky cls(j,i); return fn(j, i-1); } 的析构函数。

有时编译器可以看到析构函数没有外部可见的副作用(因此可以提前完成),但通常它不能。

一种特别常见的形式是cls实际上是Funky或类似的。

答案 3 :(得分:11)

大多数编译器在调试版本中不进行任何优化。

如果使用VC,请尝试启用PDB信息的发布版本 - 这将让您跟踪优化的应用程序,您应该希望看到您想要的内容。但请注意,调试和跟踪优化的构建将使您四处奔走,并且通常无法直接检查变量,因为它们只会在寄存器中结束或完全被优化掉。这是一次“有趣”的体验......

答案 4 :(得分:7)

正如Greg所提到的,编译器不会在调试模式下执行此操作。调试版本比prod版本更慢,但它们不应该更频繁地崩溃:如果你依赖于尾部调用优化,它们可能就是这样做的。因此,通常最好将尾调用重写为普通循环。 : - (