3.5 栈操作
栈是 x86-64 中管理函数调用、局部变量和临时数据的重要数据结构。栈在内存中向下增长(向低地址方向扩展)。
栈的特点
- 后进先出(LIFO):最后压入的数据最先弹出
- 向下增长:
pushq减小%rsp,popq增加%rsp %rsp(栈指针寄存器)始终指向当前栈的栈顶
栈的内存布局:
高地址
+-----------------+
| 调用者栈帧 |
+-----------------+ <--- %rbp (可选基址)
| 被调用者栈帧 |
+-----------------+ <--- %rsp (始终指向栈顶)
| ... |
低地址
pushq 指令
pushq 源操作数等价操作(两步骤):
subq $8, %rsp # 1. 栈指针减 8(分配空间)
movq 源操作数, (%rsp) # 2. 将值写入新栈顶执行效果
假设初始 %rsp = 0x1000,%rax = 0x1234:
pushq %rax
# 1. %rsp = 0x1000 - 8 = 0x0FF8
# 2. 内存[0x0FF8] = 0x1234pushq 之前:
地址 0x1000 ┌──────────────┐
│ ... │
└──────────────┘
pushq 之后:
地址 0x0FF8 ┌──────────────┐ ← %rsp
│ 0x1234 │
地址 0x1000 ├──────────────┤
│ ... │
└──────────────┘
pushq 的操作数类型
pushq $0xFF # 压入立即数
pushq %rax # 压入寄存器
pushq (%rdi) # 压入内存中的值
pushq 8(%rbp) # 压入栈上的值popq 指令
popq 目的操作数等价操作(两步骤):
movq (%rsp), 目的操作数 # 1. 从栈顶读取值
addq $8, %rsp # 2. 栈指针加 8(回收空间)执行效果
popq %rbx
# 1. %rbx = 内存[0x0FF8] = 0x1234
# 2. %rsp = 0x0FF8 + 8 = 0x1000popq 之前:
地址 0x0FF8 ┌──────────────┐ ← %rsp
│ 0x1234 │
└──────────────┘
popq 之后:
地址 0x0FF8 ┌──────────────┐
│ 0x1234 │ (数据仍然存在,但已无效)
地址 0x1000 ├──────────────┤ ← %rsp
│ ... │
└──────────────┘
注意:
popq后栈上的数据物理上仍然存在,但逻辑上属于无效区域。下次pushq会覆盖该位置。
pushq %rsp 的特殊语义
不同处理器对 pushq %rsp 的行为定义不同,x86-64 采用 Intel 语义:
pushq %rsp
# Intel 语义(x86-64 采用):先减 rsp,再存原始 rsp 的值
# 等价于:
# temp = %rsp # 保存原始值
# %rsp = %rsp - 8 # 分配空间
# memory[%rsp] = temp # 写入原始值popq %rsp 的特殊语义
popq %rsp
# 等价于:
# %rsp = memory[%rsp] # 读取栈顶值作为新栈指针
# %rsp = %rsp + 8 # 然后加 8这实际上是将栈顶的值弹出作为新的栈指针——一种栈切换操作。
栈操作的典型用途
1. 保存和恢复寄存器
func:
pushq %rbx # 保存 %rbx 到栈
pushq %rbp # 保存 %rbp 到栈
# ... 函数体,可以使用 %rbx 和 %rbp ...
popq %rbp # 恢复 %rbp
popq %rbx # 恢复 %rbx
ret2. 临时数据存储
pushq %rax # 临时保存 %rax
# ... 使用 %rax 做其他事情 ...
popq %rdx # 将之前保存的值恢复到 %rdx3. 函数调用与返回
call 指令隐式 pushq 返回地址,ret 指令隐式 popq 返回地址。详见 。
栈操作与函数调用
在函数调用中,栈的典型变化:
# 调用前:%rsp = 0x1000
call func # pushq 返回地址; %rsp = 0x0FF8
func:
pushq %rbp # 保存旧 %rbp; %rsp = 0x0FF0
movq %rsp, %rbp # 设置新 %rbp
subq $16, %rsp # 分配局部变量空间; %rsp = 0x0FE0
# 函数体...
movq %rbp, %rsp # 回收局部变量空间; %rsp = 0x0FF0
popq %rbp # 恢复 %rbp; %rsp = 0x0FF8
ret # popq 返回地址; %rsp = 0x1000相关笔记
-
- %rsp 栈指针寄存器
-
- 栈帧的完整结构
-
- call/ret 与栈操作的关系
-
- 使用栈保存寄存器
链接到
- 上一个知识点:3.4 数据传送指令
- 下一个知识点:3.6 lea指令