一段 C 代码如何经汇编、调用约定、栈帧变成 CPU 执行的指令序列。
语法两大方言(gcc -S 默认 AT&T,objdump -d 也是):
| AT&T | Intel | |
|---|---|---|
| 操作数顺序 | 源在前,目的在后 mov %rax, %rbx | 目的在前 mov rbx, rax |
| 寄存器前缀 | %rax | rax |
| 立即数前缀 | $5 | 5 |
| 内存寻址 | (%rbx) | [rbx] |
| 大小后缀 | movq/movl | mov qword ptr |
通用寄存器:64 位 rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp、r8~r15(低 32 位 eax、ebx…)。约定角色见 10.3:
mov rax, [rbx+8] ; rax = *(rbx + 8),变址寻址(第 4.2 节)
lea rax, [rbx+rcx*4] ; rax = rbx + rcx*4,只算地址不访存
add rax, rbx ; rax += rbx,置标志位(第 3.6 节)
cmp rax, rbx ; 比较 = 减法,只置标志位不写回
jne target ; ZF==0 则跳(相对寻址,第 4.2 节)
注意:
lea只算地址不读内存,常被编译器拿来做纯算术(lea rax, [rbx+rbx*4+3]一次算5x+3),因不碰标志位、不访存,比mul+add快——印证第 3.5 节「乘法很贵,用移位/lea 替代」。
int square(int x) { return x * x; }
编译成汇编(gcc -O2 -S):
square:
imul edi, edi ; x *= x(参数 x 已在 edi,第 1 参数寄存器)
mov eax, edi ; 返回值放 eax
ret ; 弹出返回地址,跳回调用者
一条 C 语句可能对应几条、也可能零条指令(被优化掉);变量名消失只剩寄存器/栈偏移;return = 结果塞进 eax 再 ret。
x86-64 Linux 的 ABI:
rdi, rsi, rdx, rcx, r8, r9;第 7 个起放栈上([rbp+16] 起)。rax(64 位)或 eax(32 位)。rbx, rbp, r12~r15——被调函数要用须先保存、退出前恢复。rax, rcx, rdx, rsi, rdi, r8~r11——调用者在乎则 call 前自己保存。call 前 rsp 须 16 字节对齐(进函数时 rsp+8 是 16 倍数)。rsp 下方 128 字节给被调函数免 sub rsp 直接用的临时区(叶子函数优化)。对比:caller-saved vs callee-saved = 「谁保存谁负责」。编译器把跨调用还活着的值放 callee-saved(被调者会保存)、一次性临时值放 caller-saved。故内联汇编改
rbx必须先 push,改rax不必。
; 函数序言(prologue)
push rbp ; ① 保存调用者 rbp
mov rbp, rsp ; ② 建立自己的帧底
sub rsp, 16 ; ③ 为局部变量留 16 字节
; ... 函数体:局部变量 [rbp-4]、参数 [rbp+8/16/...]
; 尾声(epilogue)
mov rsp, rbp ; ④ 丢弃局部变量
pop rbp ; ⑤ 恢复调用者 rbp
ret ; ⑥ 弹出返回地址,跳回
这六步就是 C 指针课程第 2 章栈帧图的机器级翻译:
call 压返回地址([rbp+8])、ret 弹回——对应「返回地址」。sub rsp, N 一次留出、mov rsp, rbp 一次丢弃——对应「局部变量生命周期 = 函数调用期间」。ret 后 rsp 弹回,[rbp-4] 已归还,下次 call/push 就覆盖。注意:开
-O2编译器可能省略帧指针(-fomit-frame-pointer),不用rbp直接用rsp寻址——「栈帧」此刻是纯约定而非硬件结构。
| 前面章节的概念 | 在本章/汇编里的落点 |
|---|---|
| 第 2 章 补码、标志位 | cmp/add 的结果与 CF/OF/SF/ZF |
| 第 3 章 ALU、移位 | add/imul/sar/shl 指令 |
| 第 4 章 ISA、寻址 | mov [rbx+8]、lea [rbx+rcx*4]、jne 相对寻址 |
| 第 5 章 数据通路、PC/IR | 每条指令在取指-执行循环里流转 |
| 第 6 章 流水线、分支预测 | jne 触发预测,预测错被冲刷 |
| 第 7/8 章 Cache、虚拟内存 | 指令/栈帧都走 Cache、经 MMU 翻译 |
| 第 9 章 中断 | 系统调用 syscall、缺页异常打断当前指令流 |
| C 指针第 2 章 栈帧 | 10.4 的序言/尾声六步 |