尾调用

作者: 凉夜lrs | 来源:发表于2024-07-24 18:02 被阅读0次

转载自:https://hypc-pub.github.io/lua-tutorial/chapter06/tail_calls.html

定义

Lua中函数的一个有趣的特征是可以正确的处理尾调用(proper tail recursion)。
尾调用是一种类似在函数结尾的 goto 调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。

function f()
    return g()
end

g 的调用是尾调用。
例子中 f 调用 g 后不会再做任何事情,这种情况下当被调用函数 g 结束时程序不需要返回到调用者 f; 所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈, 我们称这种语言支持正确的尾调用。

特点

由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的。 例如下面调用不论 n 为何值不会导致栈溢出。

function foo(n)
    if n > 0 then return foo(n - 1) end
end

需要注意的是:必须明确什么是尾调用。
一些调用者函数调用其他函数后也没有做其他的事情但不属于尾调用。比如:

function f(x)
    g(x)
    return
end

上面这个例子中 f 在调用 g 后,不得不丢弃 g 的返回值,所以不是尾调用,同样的下面几个例子也不是尾调用:

g(x)                    -- 没有return语句的明确提示
return g(x) + 1         -- 在g()函数返回之后仍需执行一次加一的指令
return x or g(x)        -- 如果g()函数返回多个值,该操作会强制要求g()函数只返回一个值
return (g(x))           -- 同上

Lua 中类似 return g(...) 这种格式的调用是尾调用。 但是 g 和 g 的参数都可以是复杂表达式,因为 Lua 会在调用之前计算表达式的值。 例如下面的调用是尾调用:

return x[i].foo(x[j] + a*b, i + j)

如果没有正确的尾调用,每次调用都要创建一个栈,多次调用后可能导致栈溢出。 但正确的尾调用可以无限制的尾调用,因为每次尾调用只是一个 goto 到另外一个函数并不是传统的函数调用。

栈溢出

调用栈(英语:Call stack,港台称“呼叫堆叠”)别称有:执行栈(execution stack)、控制栈(control stack)、运行时栈(run-time stack)与机器栈(machine stack),是计算机科学中存储有关正在执行的子程序的消息的栈。英文有时直接简称“栈”(the stack),但栈中不一定仅存储子程序消息。几乎所有计算机程序都依赖于调用栈,而高级语言一般将调用栈的细节隐藏至后台。
调用栈最经常被用于存放子程序的返回地址。在调用任何子程序时,主程序都必须暂存子程序执行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回地址就必须存入调用栈,在其自身执行完毕后再行取回。在递归程序中,每一层次递归都必须在调用栈上增加一条地址,因此如果程序出现无限递归(或仅仅是过多的递归层次),调用栈就会产生栈溢出。
堆栈溢出(英语:stack overflow)在计算机科学中是指使用过多的存储器时导致调用堆栈产生的溢出,也是缓冲区溢出中的一种。堆栈溢出的产生是由于过多的函数调用,导致使用的调用堆栈大小超过事先规划的大小,覆盖其他存储器内的资料,一般在递归中产生。堆栈溢出很可能由无限递归(Infinite recursion)产生,但也可能仅仅是过多的堆栈层级。

相关文章

  • 什么是尾调用?什么是尾递归?尾调用的优化?尾递归优化?

    尾调用优化 尾递归(尾调用优化)

  • 尾调用 & 尾递归 & 尾调用优化

    参考 递归和栈帧的调用原理[https://blog.csdn.net/poiuyds/article/detai...

  • 尾调用和尾调用递归

    来自于阮一峰[https://www.ruanyifeng.com/] # ECMAScript 6 入门[ht...

  • 尾调用

    1.什么是调用帧? 我们知道函数调用会在内存生成一个“调用记录”,又称“调用帧”,保存调用位置和内部变量等信息。如...

  • 尾调用

    在很多时候,递归算法能够解决很多问题,但是在以前它是一种以空间换取性能的办法,如果改成循环,往往理解起来会很困难,...

  • 尾调用

    什么是尾调用 尾调用(Tail call)是函数式编程中的一个重要概念,本身非常简单,就是指某个函数的最后一步是调...

  • 尾递归优化

    尾调用 尾调用指某个函数的最后一步是调用另一个函数。 尾调用不一定出现在函数尾部,只要是最后一步操作即可。 尾调用...

  • 尾调用和尾递归

    尾调用 1. 定义 尾调用是函数式编程中一个很重要的概念,当一个函数执行时的最后一个步骤是返回另一个函数的调用,这...

  • 尾调用,尾递归优化

    函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在...

  • 尾调用和尾递归

    ES6 有两个新的东西,前端面试的时候偶尔会问道。之前也有在阮一峰的书上看过几次,但是没有统一归纳学习,今天归纳了...

网友评论

      本文标题:尾调用

      本文链接:https://www.haomeiwen.com/subject/vogehjtx.html