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:
retfor 循环的完整翻译模式
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 # 无条件跳回,永不退出相关笔记
-
- 循环中的跳转指令
-
- 循环条件的判断基础
-
- 循环体内部的条件分支
链接到
- 上一个知识点:3.10 条件分支
- 下一个知识点:3.12 switch语句