尾递归是尾调用的一种特殊情况。尾部调用是编译器可以看到的,从被调用函数返回时无需执行任何操作-本质上将被调用函数的返回变为自己的。编译器通常可以执行一些堆栈修复操作,然后跳转(而不是调用)到被调用函数的第一条指令的地址。
除了消除一些回叫之外,与此有关的一大优点是,您还可以减少堆栈使用量。在某些平台上或在OS代码中,堆栈可能会非常有限,而在台式机中x86 CPU这样的高级计算机上,减少堆栈使用量会提高数据缓存性能。
尾递归是被调用函数与调用函数相同的地方。这可以变成循环,这与上面提到的尾调用优化中的跳转完全相同。由于这是相同的功能(被调用方和调用方),因此在跳转之前需要完成的堆栈固定操作较少。
下面显示了执行递归调用的通用方法,这对于编译器而言更难于使其成为循环:
int sum(int a[], unsigned len) {
if (len==0) {
return 0;
}
return a[0] + sum(a+1,len-1);
}
这非常简单,以至于许多编译器可能无论如何都可以解决这个问题,但是正如您所看到的,在被调用sum的返回值返回一个数字之后,还需要进行一次加法运算,因此不可能进行简单的尾部调用优化。
如果您这样做:
static int sum_helper(int acc, unsigned len, int a[]) {
if (len == 0) {
return acc;
}
return sum_helper(acc+a[0], len-1, a+1);
}
int sum(int a[], unsigned len) {
return sum_helper(0, len, a);
}
您将能够利用两个函数中的调用作为尾部调用。求和函数的主要工作是移动值并清除寄存器或堆栈位置。sum_helper完成所有数学运算。
自从您在问题中提到C ++以来,我将提到一些特殊的事情。C ++对您隐藏了一些C不能做到的事情。在这些析构函数中,最主要的是阻碍尾调用的方式。
int boo(yin * x, yang *y) {
dharma z = x->foo() + y->bar();
return z.baz();
}
在此示例中,对baz的调用实际上不是尾部调用,因为从baz返回后需要销毁z。我相信,即使在调用过程中不需要变量的情况下,C ++规则也可能使优化更加困难,例如:
int boo(yin * x, yang *y) {
dharma z = x->foo() + y->bar();
int u = z.baz();
return qwerty(u);
}
从qwerty返回后,z可能必须销毁。
另一件事是隐式类型转换,它也可以在C语言中发生,但在C ++中可能更为复杂和常见。例如:
static double sum_helper(double acc, unsigned len, double a[]) {
if (len == 0) {
return acc;
}
return sum_helper(acc+a[0], len-1, a+1);
}
int sum(double a[], unsigned len) {
return sum_helper(0.0, len, a);
}
sum对sum_helper的调用不是尾部调用,因为sum_helper返回双精度数,并且sum需要将其转换为int。
在C ++中,很常见的是返回一个对象引用,该对象引用可能具有各种不同的解释,每种解释都可能是不同的类型转换,例如:
bool write_it(int it) {
return cout << it;
}
这是对cout.operator <
编辑:
值得一提的是,可以在C语言中进行尾部调用优化的主要原因是,编译器知道被调用函数将其返回值存储在与调用函数必须确保其返回值相同的位置。存储在。