我刚刚讨论了以下两个C代码的讨论:
有关环路:
#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
所以(基本上是简单而不是非常严格的)基准测试显示它确实看起来确实处于相同的时间顺序。
答案 0 :(得分:4)
只需反汇编代码,看看发生了什么。没有优化,我明白了:
0x0040150B cmpl $0x0,0x10(%rbp)
0x0040150F jle 0x401523 <recursive+35>
0x00401511 mov 0x10(%rbp),%eax
0x00401514 sub $0x1,%eax
0x00401517 mov %eax,%ecx
0x00401519 callq 0x401500 <recursive>
但是使用-O1,-O2或-O3我得到了这个:
0x00402D09 mov $0x2ffff,%edx
这与尾部优化没有任何关系,而是更激进的优化。编译器只是简单地内联整个函数并预先计算结果。
这可能是您在所有不同的基准测试案例中得出相同结果的原因。