Answers:
是的,某些语言和编译器会将递归逻辑转换为非递归逻辑。这称为尾部调用优化 -请注意,并非所有递归调用都是尾部调用可优化的。在这种情况下,编译器会识别以下形式的函数:
int foo(n) {
...
return bar(n);
}
在这里,该语言能够识别出返回的结果是来自另一个函数的结果,并将具有新堆栈框架的函数调用更改为跳转。
实现经典析因方法:
int factorial(n) {
if(n == 0) return 1;
if(n == 1) return 1;
return n * factorial(n - 1);
}
是不是因为回报必要的检查尾调用optimizatable。
为了使该尾调用可优化,
int _fact(int n, int acc) {
if(n == 1) return acc;
return _fact(n - 1, acc * n);
}
int factorial(int n) {
if(n == 0) return 1;
return _fact(n, 1);
}
使用以下代码进行编译gcc -O2 -S fact.c
(-O2是启用编译器中的优化所必需的,但是,随着-O3的更多优化,人类难以阅读...)
_fact:
.LFB0:
.cfi_startproc
cmpl $1, %edi
movl %esi, %eax
je .L2
.p2align 4,,10
.p2align 3
.L4:
imull %edi, %eax
subl $1, %edi
cmpl $1, %edi
jne .L4
.L2:
rep
ret
.cfi_endproc
可以在segment中看到.L4
,jne
而不是a call
(使用新的堆栈框架进行子例程调用)。
请注意,这是使用C完成的。java中的尾调用优化非常困难,并且取决于JVM的实现-tail-recursion + java和tail-recursion +优化是浏览的良好标记集。您可能会发现其他JVM语言可以优化尾递归更好(TRY的Clojure(这需要RECUR以尾调用优化),或斯卡拉)。
小心踩一下。
答案是肯定的,但并非总是如此,并非全部。这项技术的名称有所不同,但是您可以在此处和Wikipedia中找到一些非常确定的信息。
我更喜欢使用“尾部呼叫优化”这个名称,但还有其他名称,有些人会混淆该术语。
也就是说,有几件重要的事情要实现:
为了优化尾部调用,尾部调用需要调用时已知的参数。这意味着,如果参数之一是对函数本身的调用,则无法将其转换为循环,因为这将需要所述循环的任意嵌套,而该嵌套在编译时无法扩展。
C#无法可靠地优化尾调用。IL具有F#编译器将发出的指令,但是C#编译器将不一致地发出它,并且根据JIT的情况,JIT可能会也可能不会这样做。所有迹象表明,您不应该依赖在C#中优化尾调用,这样做的风险很大,而且确实存在
return recursecall(args);
递归的情况下,它才有效,通过创建显式堆栈并将其缩减下来,可以实现更复杂的操作,但是我怀疑它们是否会