📄 一句话概括
栈是自动管理的一块内存,用 pushl 和 popl 两条指令操作;它的规矩是后进先出——函数调用、局部变量、返回地址全靠这条规矩活着。
📝 本节和前面几节的关系
10.5 说的是在寄存器和内存之间搬,这一节专门讲其中最有规矩的那块内存。
一、栈:一块很守规矩的内存
普通的内存是这样的:你想写哪儿就写哪儿,随心所欲。 栈不一样,它只允许你从一头进出——这个头叫栈顶。 放进去叫压栈(push),取出来叫出栈(pop)。 因为只有一头,所以顺序被天然定死了:最后放进去的,最先被取出来。
二、两条指令,一个规矩
pushl 值——把一个值压到栈顶。
popl 目标——把栈顶的值取出来,放到目标里。
栈顶在哪儿,由一个叫 esp 的寄存器记着(1.5 讲过,它每压一次就动一点)。
拿一串 push 和 pop 跑一遍,栈怎么长、值怎么出来,全都看得见。
三、实测:先取出 2,再取出 1
先把 1 压进去,再把 2 压进去——栈里从底到顶是 [1, 2]。 接着弹出两次:第一次弹出的是 2,第二次才是 1。 这就是后进先出(LIFO)——最后进去的,最先出来。 有人说栈是【一摞盘子】——只能从最上面拿,这个比喻在这里很准。
四、为什么函数离不开它
因为函数调用需要一个【能记住来路】的地方,而且天生就是层层嵌套的。 A 调 B、B 又调 C——每层都把返回地址押在栈上。 C 先返回、再轮到 B、最后回到 A——正好是后进先出的顺序,一点不用额外安排。 所以后进先出不是巧合,它是【嵌套调用】这件事在内存上最自然的形状。
五、动手 Demo:将数据存入栈中
栈是自动管理的一块内存,用 pushl 和 popl 操作。 跑一串 push 与 pop,看栈是怎么长的。
prog = [
("movl", "$1", "%eax"),
("movl", "$2", "%ebx"),
("pushl", "%eax"),
("pushl", "%ebx"),
("popl", "%ecx"),
("popl", "%edx"),
]
vm = VM(prog)
while vm.pc < len(prog):
shown, before, after = vm.step()
print(shown, "栈=", vm.stack, "ecx=", after["ecx"], "edx=", after["edx"])
实跑输出:
--------------------------------------------------------------------
将数据存入栈中
--------------------------------------------------------------------
**栈是自动管理的一块内存**,用两条指令操作:
pushl 把一个值压到栈顶
popl 把栈顶的值取出来
跑一串 push 与 pop,看栈是怎么长的:
指令 eax ebx ecx edx 栈(底→顶)
--------------------------------------------------------------
movl $1, %eax 1 0 0 0 (空)
movl $2, %ebx 1 2 0 0 (空)
pushl %eax 1 2 0 0 [1]
pushl %ebx 1 2 0 0 [1, 2]
popl %ecx 1 2 2 0 [1]
popl %edx 1 2 2 1 (空)
**注意最后两步取出来的顺序:先取出 2,再取出 1。**
这就是**后进先出**——**函数调用、局部变量、返回地址,全都靠这条规矩活着。**
1.5 讲过的【把返回地址压进栈、跳过去;返回就是把它弹出来】,靠的就是这条规矩。
实测:压入 1、2 之后栈是 [1, 2];弹出两次先得到 2,再得到 1。 后进先出——函数调用、局部变量、返回地址,全靠这条规矩活着。
30 秒实验
- 在纸上推一遍:再压一个 3,然后连弹三次,顺序是什么。
- 想一想:如果栈不是后进先出,函数嵌套调用还能不能成立。
常见误区
- 以为栈和普通内存一样可以随便读写。它只允许从栈顶进出。
- 以为后进先出是人为规定的。它是嵌套调用最自然的形状。
自测三问(点开对答案)
1. 栈的两条操作指令是什么? —— pushl 压栈、popl 出栈,都只作用于栈顶。 2. 后进先出是什么意思? —— 最后压进去的值最先被弹出来,栈里从底到顶是 [1, 2],弹出顺序就是 2、1。 3. 为什么函数调用离不开栈? —— 因为要记住返回地址,而嵌套调用天然是后进先出的顺序,栈正好匹配。- ☐ 下一节(10.7 函数调用的工作原理)
相关笔记
- 函数调用的原理 — 这套规矩在硬件上怎么用
- 函数调用的工作原理 — 压进去到底是为了什么
- 加载时生成的栈和堆 — 栈什么时候被建起来
- 最常用的movl指令 — 搬数据的那条指令
- ⚙️ 程序是怎样跑起来的 · 索引