Skip to content

程序的机器级表示

考情分析

2014、2017、2019、2023、2024 年均以综合题形式考查了 C 代码到汇编的翻译。高频考点:循环语句的汇编实现、过程调用的栈帧布局、call/ret 的操作细节。

选择语句的机器级表示

if-else 语句的通用翻译模式(goto 形式):

    t = test_expr;
    if (!t) goto false;
    then_statement
    goto done;
false:
    else_statement
done:

编译器使用 cmp/test 设置标志位,再用条件转移指令实现分支选择。

示例

c
int get_cont(int *p1, int *p2) {
    if (p1 > p2) return *p2;
    else return *p1;
}

参数 p1 在 [ebp+8],p2 在 [ebp+12],返回值放 eax。

asm
mov eax, dword ptr [ebp+8]    ; 加载 p1
mov edx, dword ptr [ebp+12]   ; 加载 p2
cmp eax, edx                  ; 比较 p1 和 p2
jbe .L1                       ; 若 p1 <= p2,跳到 L1(无符号比较)
mov eax, dword ptr [edx]      ; 取 *p2 作为返回值
jmp .L2
.L1:
mov eax, dword ptr [eax]      ; 取 *p1 作为返回值
.L2:

指针比较按无符号整数处理,因此使用 jbe(无符号"小于等于")。

循环语句的机器级表示

三种循环都可以转换为"条件测试 + 条件跳转"的形式。编译器通常统一转换为 do-while 结构。

do-while

loop:
    body_statement
    t = test_expr;
    if (t) goto loop;

循环体至少执行一次。

while

先判断再进入循环体,转换为带前置判断的 do-while:

    t = test_expr;
    if (!t) goto done;
loop:
    body_statement
    t = test_expr;
    if (t) goto loop;
done:

for

for (init; test; update) body 等价于:

    init;
    t = test;
    if (!t) goto done;
loop:
    body;
    update;
    t = test;
    if (t) goto loop;
done:

示例

c
int nsum_for(int n) {
    int i, result = 0;
    for (i = 1; i <= n; i++)
        result += i;
    return result;
}
asm
mov ecx, dword ptr [ebp+8]   ; 加载参数 n
mov eax, 0                    ; result = 0
mov edx, 1                    ; i = 1
cmp edx, ecx                  ; 比较 i 与 n
jg .L2                        ; 若 i > n,跳过循环
.L1:
add eax, edx                  ; result += i
add edx, 1                    ; i++
cmp edx, ecx                  ; 比较 i 与 n
jle .L1                       ; 若 i <= n,继续循环
.L2:

result 分配到 eax(同时是返回值),i 分配到 edx。int 型比较属于有符号,使用 jg/jle。

过程调用的机器级表示

调用过程

假设过程 P(调用者)调用过程 Q(被调用者):

  1. P 将参数放到 Q 能访问的位置(压入栈中)
  2. P 执行 call Q:将返回地址(call 的下一条指令地址)压栈,跳转到 Q
  3. Q 建立栈帧:push ebp; mov ebp, esp; sub esp, N
  4. Q 执行函数体
  5. Q 将返回值放入 eax,执行 leave(或等价操作)恢复栈帧
  6. Q 执行 ret:弹出返回地址,跳回 P

栈帧结构

栈从高地址向低地址增长。EBP 指向当前栈帧底部(保存旧 EBP 的位置),ESP 指向栈顶。

高地址
┌─────────────────┐
│   参数 n         │  [ebp+12]
│   参数 1         │  [ebp+8]
│   返回地址        │  [ebp+4]   ← call 指令压入
├─────────────────┤
│   旧 EBP         │  [ebp]     ← push ebp
├─────────────────┤
│   局部变量 1      │  [ebp-4]
│   局部变量 2      │  [ebp-8]
│   ...            │
│   被调用者保存寄存器│
│   为下次 call 准备 │
│   的参数区        │
└─────────────────┘  ← ESP(栈顶)
低地址

寄存器保存约定

类别寄存器谁负责保存
调用者保存EAX, ECX, EDXP 在 call 前保存(如果之后还要用)
被调用者保存EBX, ESI, EDIQ 使用前先 push,返回前 pop 恢复

完整示例

c
int add(int x, int y) { return x + y; }

int caller() {
    int temp1 = 125, temp2 = 80;
    int sum = add(temp1, temp2);
    return sum;
}

caller 的汇编代码

asm
push ebp                       ; 保存调用者的 EBP
mov ebp, esp                   ; 建立新栈帧
sub esp, 24                    ; 分配局部变量和参数空间
mov dword ptr [ebp-12], 125    ; temp1 = 125
mov dword ptr [ebp-8], 80      ; temp2 = 80
mov eax, dword ptr [ebp-8]     ; 加载 temp2
mov [esp+4], eax               ; 参数 2 入栈(高地址)
mov eax, dword ptr [ebp-12]    ; 加载 temp1
mov [esp], eax                 ; 参数 1 入栈(低地址)
call add                       ; 调用 add,返回值在 eax
mov dword ptr [ebp-4], eax     ; sum = 返回值
mov eax, dword ptr [ebp-4]     ; 将 sum 作为 caller 的返回值
leave                          ; mov esp,ebp + pop ebp
ret                            ; 弹出返回地址,返回

add 的汇编代码(含机器码)

asm
8048469: 55        push ebp
804846a: 89 e5     mov ebp, esp
804846c: 8b 45 0c  mov eax, dword ptr [ebp+12]   ; 加载 y
804846f: 8b 55 08  mov edx, dword ptr [ebp+8]    ; 加载 x
8048472: 8d 04 02  lea eax, [edx+eax]            ; eax = x + y
8048475: 5d        pop ebp
8048476: c3        ret

add 函数用 lea 指令完成加法运算(lea 计算地址但不访存,这里利用地址计算实现 x+y)。

leave 指令

等价于:

asm
mov esp, ebp    ; 释放局部变量空间,ESP 回到保存旧 EBP 的位置
pop ebp         ; 恢复调用者的 EBP

考点清单

  • if-else:cmp + 条件跳转(真分支顺序执行,假分支 goto)
  • 循环:for/while 都可以转换为 do-while + 前置判断
  • call = push 返回地址 + jmp;ret = pop 返回地址 + jmp
  • 栈帧:EBP 指向底部(固定),ESP 指向栈顶(动态变化)
  • 参数通过 [ebp+8][ebp+12] 等访问(32 位系统中参数每个占 4 字节)
  • 返回值放在 EAX 中
  • EAX/ECX/EDX 为调用者保存,EBX/ESI/EDI 为被调用者保存
  • leave = mov esp,ebp + pop ebp

真题练习