📄 一句话概括
函数调用只多了两个动作——把返回地址压进栈、跳过去;返回就是把它弹出来。
📝 本节和前面几节的关系
1.4 讲 PC 被改写,这一节讲被改写时怎么还能回得来。
一、调用一个函数,机器要做几件事
跳过去是必须的(改 PC)——但光跳过去不行:回来的时候不知道回哪。 所以调用之前,得先把【下一条指令的地址】存起来。存哪儿?栈。 教材的表述很简洁:call 指令会把下一条指令的地址存入栈。 所以函数调用在硬件上比普通跳转只多了一件事:多存一个地址。
二、栈:一摞只能从上头取的盘子
栈是一段内存,但它有个特殊规矩:只有顶端一个开口,放也从顶上、取也从顶上。 结果就是后放的先取出(LIFO,后进先出)。 这个规矩看着绕,用在函数调用上却严丝合缝——最后调用的那个函数,本来就该最先返回。 栈寄存器(esp)的作用就是记住【当前顶端在哪】——它每压一次就动一点。
三、嵌套调用看着最清楚
A 调 B、B 调 C,栈里依次压进:A 的下一条地址、B 的下一条地址。 C 执行完 return → 弹出 B 的下一条地址,回到 B。 B 执行完 return → 弹出 A 的下一条地址,回到 A。 一层层原路退回,顺序天然正确——不需要谁去做额外的记录。 这也是为什么栈被叫做【函数调用的账本】。
四、局部变量也在栈上
函数里的局部变量,通常也放在栈上——跟着这一次调用一起生、一起灭。 所以【函数一结束,局部变量就没了】不是被谁清掉了,而是栈顶回退,那块地方被收回。 顺带解释了另一件事:递归太深会把栈压满,程序崩的常见原因就是这个。
五、动手 Demo:函数调用——把返回地址压进栈
call 只做两件事:把下一条指令的地址压进栈,然后跳过去。 return 只做一件事:从栈里弹出地址,跳回那里。
stack = []
print("执行到 0x0100 的 call -> 压入返回地址,PC 跳到 0x0300")
stack.append("0x0105")
print(" 栈里的内容:", stack)
print("在函数里干活……")
ret = stack.pop()
print("遇到 return -> 弹出 %s,PC 跳回" % ret)
print(" 栈里的内容:", stack)
实跑输出:
--------------------------------------------------------------------
函数调用——把返回地址压进栈
--------------------------------------------------------------------
**call 只做两件事:把下一条指令的地址压进栈,然后跳过去。**
**return 只做一件事:从栈里弹出地址,跳回那里。**
情形:主程序在 0x0100 处调用位于 0x0300 的函数
步骤 CPU 在做什么 PC 栈里的内容
------------------------------------------------------------------
1 执行到 0x0100 的 call 0x0300 0x0105(返回地址)
2 在函数里干活…… 0x0301 0x0105
3 遇到 return 0x0105 (弹空了)
4 继续从 0x0105 往下执行 0x0105 (空)
两个关键点:
压进栈的是**调用点的下一条地址**(0x0105),不是调用点本身
函数嵌套时,栈里会叠起一层层返回地址——最后调用的最先返回
**所谓【函数调用】,不过是把 PC 存起来、跳走、再取回来。**
这也是为什么栈寄存器(esp)和 PC 一起,成了程序里最忙的两个寄存器。
压进栈的是【调用点的下一条地址】,不是调用点本身。 所谓函数调用,不过是把 PC 存起来、跳走、再取回来。
30 秒实验
- 写两个互相调用的函数,在纸上画出栈里依次压进了什么。
- 把递归的层数一直加大,观察程序什么时候出错——那就是栈被压满了。
常见误区
- 以为函数调用是【把那段代码复制过去执行】。代码只有一份,压进栈的是返回地址。
- 以为递归崩掉是算力不够。绝大多数情况是栈被压满了。
自测三问(点开对答案)
1. call 指令做了哪两件事? —— 把下一条指令的地址压进栈,然后跳转到函数入口。 2. 为什么嵌套调用能正确返回? —— 栈是后进先出,返回地址一层层弹出,顺序天然对应【后调用的先返回】。 3. 局部变量为什么跟着函数调用一起消失? —— 它们放在栈上;调用结束后栈顶回退,那块内存被回收。- ☐ 下一节(1.6 用基址和变址实现数组)
相关笔记
- 条件分支和循环的原理 — 改 PC 的另一半故事
- 用基址和变址实现数组 — 内存的另一段常见用法
- ⌨️ 编程原理 · 索引 — 函数与调用在其他语言里长什么样
- 🔵 计算机基础 · 索引 — 硬件底层的入门课
- ⚙️ 程序是怎样跑起来的 · 索引