哪些C ++编译器进行尾递归优化?

时间:2020-03-05 18:44:53  来源:igfitidea点击:

在我看来,用C和C ++进行尾递归优化会很好地工作,但是在调试时,我似乎从未看到指示这种优化的帧堆栈。这很好,因为堆栈告诉我递归的深度。但是,优化也会很好。

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

如何告诉编译器执行此操作?

  • 对于MSVC:/ O2或者/ Ox
  • 对于GCC:-O2或者-O3

在某些情况下,检查编译器是否已执行此操作呢?

  • 对于MSVC,启用PDB输出以能够跟踪代码,然后检查代码
  • 对于海湾合作委员会..?

我仍然会建议如何确定某个函数是否通过编译器进行了优化(即使我放心Konrad告诉我假设也是如此)。

总是可以通过进行无限递归检查编译器是否做到了这一点,并检查它是否导致无限循环或者堆栈溢出(我用GCC这样做并发现`-O2'就足够了),但是我希望能够检查我知道将终止的某些功能。我很想有一个简单的方法来检查这个:)

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

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

解决方案

回答

当前所有主流的编译器都很好地执行了尾部调用优化(并且已经进行了十多年),即使对于相互递归的调用,例如:

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

检查编译器是否进行了优化的一种简单方法是执行一个调用,否则将导致堆栈溢出或者查看程序集的输出。

作为一个有趣的历史记录,马克·普罗布斯特(Mark Probst)在文凭论文过程中将C的尾调用优化添加到了GCC中。本文描述了实现过程中的一些有趣的警告。值得一读。

回答

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

如果使用VC,请尝试在PDB信息打开的情况下发布版本,这将使我们能够跟踪经过优化的应用程序,并希望随后可以看到想要的内容。但是请注意,调试和跟踪优化的构建会使我们无所适从,并且通常我们无法直接检查变量,因为它们只会出现在寄存器中或者完全被优化掉。这是一次"有趣"的经历...

回答

正如Greg所提到的,编译器不会在调试模式下执行此操作。调试构建比生产构建慢一些是可以的,但是它们不应更频繁地崩溃:而且,如果我们依赖尾部调用优化,它们可能会做到这一点。因此,通常最好将尾调用重写为普通循环。 :-(

回答

gcc 4.3.2完全将此函数(糟糕/琐碎的atoi()实现)内联到了main()中。优化级别为-O1. 我注意到,如果我使用它(即使将其从"静态"更改为"外部",尾部递归也会很快消失,因此程序的正确性就不必依赖它了。

#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;
}