第四十三章:函数调用与运行栈
复习
- 函数把一组步骤命名,并通过参数接收输入、通过返回值交回结果。
- 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 协议发布。