3.11 循环的汇编实现

C 语言的三种循环结构(do-while、while、for)在汇编层面都通过 条件跳转无条件跳转 的组合实现。编译器使用不同的翻译模式来优化不同场景。

do-while 循环

do-while 是后测试循环:先执行循环体,再判断条件。这是三种循环中最直接的翻译方式。

C 语言和汇编对照

// C 源代码
long fact_do(long n) {
    long result = 1;
    do {
        result *= n;
        n--;
    } while (n > 1);
    return result;
}
# 汇编翻译(直接翻译)
fact_do:
    movq    $1, %rax            # result = 1
 
.Lloop:
    imulq   %rdi, %rax          # result *= n
    subq    $1, %rdi            # n--
    cmpq    $1, %rdi            # 比较 n 和 1
    jg      .Lloop              # 如果 n > 1,继续循环
 
    ret

控制流

        ┌─────────────┐
        │ result = 1  │
        └──────┬──────┘
               ▼
        ┌─────────────┐
   ┌─── │  循环体代码   │
   │    └──────┬──────┘
   │           ▼
   │    ┌─────────────┐
   │    │  条件判断    │
   └────┤ (n > 1)     │──→ 假 → 退出
        └─────────────┘
               │
               真
               │
               └── 回到循环体

特点

  • 循环体至少执行一次
  • 翻译最直接,无需额外跳转
  • 编译器使用 do-while 作为基础模式翻译其他循环

while 循环

while 是前测试循环:先判断条件,再执行循环体。有两种翻译策略。

策略一:跳转到中间(Jump to Middle)

// C 源代码
long fact_while(long n) {
    long result = 1;
    while (n > 1) {
        result *= n;
        n--;
    }
    return result;
}
# 跳转到中间模式的翻译
fact_while:
    movq    $1, %rax            # result = 1
    jmp     .Ltest              # 跳转到条件测试
 
.Lloop:
    imulq   %rdi, %rax          # result *= n
    subq    $1, %rdi            # n--
 
.Ltest:
    cmpq    $1, %rdi            # 比较 n 和 1
    jg      .Lloop              # 如果 n > 1,执行循环体
 
    ret

控制流

        ┌─────────────┐
        │ result = 1  │
        └──────┬──────┘
               ▼
        ┌─────────────┐
   ┌─── │  条件测试    │
   │    │ (n > 1)     │──→ 假 → 退出
   │    └──────┬──────┘
   │           │ 真
   │           ▼
   │    ┌─────────────┐
   └────┤  循环体代码   │
        └─────────────┘

策略二:Guarded-Do(受保护的 do-while)

编译器先将 while 转换为受保护的 do-while 形式:用一个条件跳转跳过整个循环体,再使用 do-while 模式。

# Guarded-Do 翻译模式
fact_while_guarded:
    movq    $1, %rax            # result = 1
    cmpq    $1, %rdi            # 比较 n 和 1
    jle     .Ldone              # 如果 n <= 1,完全跳过循环 (guard)
 
.Lloop:
    imulq   %rdi, %rax          # result *= n
    subq    $1, %rdi            # n--
    cmpq    $1, %rdi            # 比较 n 和 1
    jg      .Lloop              # 如果 n > 1,继续循环
 
.Ldone:
    ret

二者对比

策略优点缺点
跳转到中间代码紧凑多一个无条件跳转
Guarded-Do循环外只有条件跳转,内部无额外跳转多一个条件判断

现代 gcc 通常使用 Guarded-Do 模式,因为它为循环体提供了更好的分支预测效果。

for 循环

for 循环被编译器转化为等价的 while 循环,再应用上述翻译策略。

// C 源代码
long fact_for(long n) {
    long result = 1;
    for (long i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

转化为 while

// 等价 while 循环
long fact_for_while(long n) {
    long result = 1;
    long i = 2;             // 初始化
    while (i <= n) {        // 测试条件
        result *= i;        // 循环体
        i++;                // 更新
    }
    return result;
}

Guarded-Do 翻译

fact_for:
    movq    $1, %rax            # result = 1
    movq    $2, %rcx            # i = 2
    cmpq    %rdi, %rcx          # 比较 i 和 n
    jg      .Ldone              # if (i > n) goto done (guard)
 
.Lloop:
    imulq   %rcx, %rax          # result *= i
    addq    $1, %rcx            # i++
    cmpq    %rdi, %rcx          # 比较 i 和 n
    jle     .Lloop              # if (i <= n) continue
 
.Ldone:
    ret

for 循环的完整翻译模式

for (init; test; update) {
    body;
}

→

init;
if (!test) goto done;
do {
    body;
    update;
} while (test);
done:

循环翻译总结

循环类型测试时机至少执行一次翻译模式
do-while后测试直接翻译
while (基础)前测试跳转到中间 / Guarded-Do
for前测试转化为 while → Guarded-Do

通用翻译模板

# Guarded-Do 模板
    [初始化]
    jmp     .Lguard
 
.Lloop:
    [循环体]
    [更新操作]
 
.Lguard:
    [条件测试]
    jXX     .Lloop              # 满足条件则继续
 
.Ldone:

嵌套循环

嵌套循环的汇编通过多层跳转实现:

// C 源代码
long nest_loop(long n) {
    long sum = 0;
    for (long i = 0; i < n; i++) {
        for (long j = 0; j < n; j++) {
            sum += i * j;
        }
    }
    return sum;
}
nest_loop:
    movq    $0, %rax            # sum = 0
    movq    $0, %rcx            # i = 0
 
.Louter_test:
    cmpq    %rdi, %rcx          # 比较 i 和 n
    jge     .Ldone              # if (i >= n) 退出
 
    movq    $0, %rdx            # j = 0
 
.Linner_loop:
    cmpq    %rdi, %rdx          # 比较 j 和 n
    jge     .Lnext_i            # if (j >= n) 退出内循环
 
    movq    %rcx, %r8
    imulq   %rdx, %r8           # i * j
    addq    %r8, %rax           # sum += i * j
    addq    $1, %rdx            # j++
    jmp     .Linner_loop
 
.Lnext_i:
    addq    $1, %rcx            # i++
    jmp     .Louter_test
 
.Ldone:
    ret

无限循环

// C 代码
void loop_forever() {
    while (1) {
        // do something
    }
}
loop_forever:
.Lloop:
    # 循环体
    jmp     .Lloop              # 无条件跳回,永不退出

相关笔记

    • 循环中的跳转指令
    • 循环条件的判断基础
    • 循环体内部的条件分支

链接到