第 10 章 · 汇编与程序运行

一段 C 代码如何经汇编、调用约定、栈帧变成 CPU 执行的指令序列。

10.1 x86-64 汇编基础

语法两大方言(gcc -S 默认 AT&T,objdump -d 也是):

AT&TIntel
操作数顺序源在前,目的在后 mov %rax, %rbx目的在前 mov rbx, rax
寄存器前缀%raxrax
立即数前缀$55
内存寻址(%rbx)[rbx]
大小后缀movq/movlmov 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 替代」。

10.2 从 C 到机器码:编译的最后一公里

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 = 结果塞进 eaxret

10.3 调用约定:System V AMD64 ABI

x86-64 Linux 的 ABI:

  • 参数传递:前 6 个整型参数依次 rdi, rsi, rdx, rcx, r8, r9;第 7 个起放栈上[rbp+16] 起)。
  • 返回值:整数/指针放 rax(64 位)或 eax(32 位)。
  • 被调用者保存(callee-saved)rbx, rbp, r12~r15——被调函数要用须先保存、退出前恢复。
  • 调用者保存(caller-saved)rax, rcx, rdx, rsi, rdi, r8~r11——调用者在乎则 call 前自己保存。
  • 栈对齐callrsp 须 16 字节对齐(进函数时 rsp+8 是 16 倍数)。
  • 红区(red zone)rsp 下方 128 字节给被调函数免 sub rsp 直接用的临时区(叶子函数优化)。

对比:caller-saved vs callee-saved = 「谁保存谁负责」。编译器把跨调用还活着的值放 callee-saved(被调者会保存)、一次性临时值放 caller-saved。故内联汇编改 rbx 必须先 push,改 rax 不必。

10.4 栈帧

; 函数序言(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 一次丢弃——对应「局部变量生命周期 = 函数调用期间」。
  • 返回局部变量地址悬空retrsp 弹回,[rbp-4] 已归还,下次 call/push 就覆盖。

注意:开 -O2 编译器可能省略帧指针-fomit-frame-pointer),不用 rbp 直接用 rsp 寻址——「栈帧」此刻是纯约定而非硬件结构。

10.5 与前面各章对照

前面章节的概念在本章/汇编里的落点
第 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 的序言/尾声六步