Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

第四十三章:函数调用与运行栈

复习

  • 函数把一组步骤命名,并通过参数接收输入、通过返回值交回结果。
  • CPU 的程序计数器必须知道下一条要执行的指令。

TL;DR

  • 调用函数时,程序需要保存返回位置和这次调用的数据。
  • 运行栈按“后放先取”的顺序保存这些信息。
  • 每次调用都会形成一个栈帧,调用结束后再撤掉它。

正文

  执行 best = larger(apples, pears) 时,程序不能只跳到 larger 的开头。它还要记住:函数完成后,应回来继续执行赋值语句后面的内容。要是函数又调用另一个函数,待返回的位置会更多。

  计算机通常在内存中留出一片连续区域,叫作运行栈(stack)。它像一摞盘子:新信息放在最上面,取走时也先取最上面的。调用函数时,程序会压入返回地址、参数或其他需要保留的内容;函数结束时,再按相反顺序取回。

栈顶 → ┌────────────────┐
       │ 当前函数的局部变量 │
       ├────────────────┤
       │ 返回到调用者的地址 │
       ├────────────────┤
       │ 调用者留下的信息   │
       └────────────────┘

  其中属于“一次调用”的那一小块记录,称为栈帧。它通常包括返回地址、参数、局部变量,以及保存的部分寄存器值。函数返回时,当前栈帧被移除,上一层调用的现场便重新露出来。

  这解释了为什么同一个函数可以同时为不同任务工作,也解释了递归函数为什么可行:函数甚至可以调用自己,只要每次调用都有自己的栈帧。不过栈不是无限的。递归没有停止条件,或调用层数极深,就会耗尽这片空间,形成“栈溢出”。

  我们现在已看见函数的运行痕迹:指令在一处,数据在别处,调用又临时占用一片栈。下一章把这些内容放进同一张程序内存地图里。

思考题

函数 A 调用 B,B 再调用 C。C 返回后为什么应先回到 B,而不是直接回到 A?

小结

知识点

  • 返回地址使函数完成后能继续调用者的后续代码。
  • 栈遵循后进先出,适合嵌套调用。
  • 栈帧保存一次函数调用所需的现场。

参考资料

  • Randal Bryant、David O’Hallaron,《深入理解计算机系统》

思考题答案(仅供参考)

  B 调用 C 时,B 的后续工作尚未完成。C 的返回地址位于栈顶,先取出它就会回到 B;B 完成后才会取回返回 A 的地址。

协议

本文采用 CC BY-NC-SA 4.0 协议发布。