【发布时间】:2010-11-17 10:52:37
【问题描述】:
Steve Yegge 在blog post 中提到了它,我不知道它是什么意思,有人可以补充我吗?
【问题讨论】:
标签: language-agnostic recursion tail-recursion tail-call-optimization program-transformation
Steve Yegge 在blog post 中提到了它,我不知道它是什么意思,有人可以补充我吗?
【问题讨论】:
标签: language-agnostic recursion tail-recursion tail-call-optimization program-transformation
尾调用消除是一种节省堆栈空间的优化。它将函数 call 替换为 goto。尾递归消除是同样的事情,但增加了函数调用自身的约束。
基本上,如果函数A 所做的最后一件事是return A(params...),那么您可以消除堆栈帧的分配,而是设置适当的寄存器并直接跳转到函数体。
考虑一个(虚构的)调用约定,它传递堆栈上的所有参数并返回某个寄存器中的值。
一些函数可以编译成(用一种想象的汇编语言):
function:
//Reading params B, C, & D off the stack
pop B
pop C
pop D
//Do something meaningful, including a base case return
...
//Pass new values for B, C, & D to a new invocation of function on the stack
push D*
push C*
push B*
call function
ret
无论上面实际做了什么,每次调用函数都会占用一个全新的堆栈帧。但是,由于在函数的尾部调用之后除了返回之外什么都没有发生,我们可以安全地优化这种情况。
导致:
function:
//Reading params B, C, & D off the stack (but only on the first call)
pop B
pop C
pop D
function_tail_optimized:
//Do something meaningful, including a base case return
...
//Instead of a new stack frame, load the new values directly into the registers
load B, B*
load C, C*
load D, D*
//Don't call, instead jump directly back into the function
jump function_tail_optimized
最终结果是一个等效的函数,可以节省大量堆栈空间,尤其是对于导致大量递归调用的输入。
我的回答需要很多想象力,但我认为您可以理解。
【讨论】:
来自here:
"...尾递归消除是一个 尾调用消除的特殊情况 其中尾调用是对 函数本身。在这种情况下 调用可以替换为跳转到 移动后的功能启动 适当的新论据 寄存器或堆栈位置..."
来自Wikipedia:
"...当一个函数被调用时,计算机必须“记住”它被调用的地方,返回地址,这样一旦调用完成,它就可以带着结果返回那个位置。通常,这个信息保存在堆栈中,返回位置的简单列表,按它们描述的调用位置到达的时间顺序排列。有时,一个函数在完成所有其他操作后做的最后一件事就是简单地调用一个函数,可能是它自己, 并返回其结果。使用尾递归,不需要记住我们调用的位置——相反,我们可以不理会堆栈,新调用的函数将直接将其结果返回给原始调用者。在这种情况下将调用转换为分支或跳转称为尾调用优化。请注意,尾调用不必在源代码中的所有其他语句之后出现在词法上;重要的是它的结果立即返回,因为 ca如果执行优化,lling 函数将永远不会有机会在调用后执行任何操作...."
【讨论】: