【发布时间】:2017-02-10 12:30:30
【问题描述】:
我刚刚讨论过以下两种 C 代码:
For循环:
#include <stdio.h>
#define n (196607)
int main() {
long loop;
int count=0;
for (loop=0;loop<n;loop++) {
count++;
}
printf("Result = %d\n",count);
return 0;
}
递归:
#include <stdio.h>
#define n (196607)
long recursive(long loop) {
return (loop>0) ? recursive(loop-1)+1: 0;
}
int main() {
long result;
result = recursive(n);
printf("Result = %d\n",result);
return 0;
}
看到这段代码,我看到recursive(loop-1)+1 并想“啊,这不是尾调用递归”,因为在对recursive 的调用完成后它还有工作要做;它需要增加返回值。
果然,没有优化,递归代码触发堆栈溢出,正如你所料。
但是,使用-O2 标志,不会遇到堆栈溢出,我认为这意味着堆栈被重用,而不是越来越多地压入堆栈 - 这是 tco。
GCC 显然可以检测到这种简单的情况(+1 为返回值)并对其进行优化,但它到底能走多远呢?
当递归调用不是要执行的最后一个操作时,gcc 可以使用 tco 优化的限制是什么?
附录:
我已经编写了代码的完全尾递归 return function(); 版本。
用 9999999 次迭代将上述内容包装在一个循环中,我想出了以下时间:
$ for f in *.exe; do time ./$f > results; done
+ for f in '*.exe'
+ ./forLoop.c.exe
real 0m3.650s
user 0m3.588s
sys 0m0.061s
+ for f in '*.exe'
+ ./recursive.c.exe
real 0m3.682s
user 0m3.588s
sys 0m0.093s
+ for f in '*.exe'
+ ./tail_recursive.c.exe
real 0m3.697s
user 0m3.588s
sys 0m0.077s
所以一个(诚然简单但不是很严格的)基准测试表明,它确实看起来确实是在相同的时间顺序中。
【问题讨论】:
-
编译器可能只是内联了函数而不是使用尾递归。使用
-S标志编译程序,看看汇编代码是什么样子的。 -
我同意@squeamishossifrage。不要启用优化,然后假设编译器做了什么。你可能会感到惊讶,这有点毫无意义。
标签: c gcc recursion optimization tail-call-optimization