控制流(Control Flow)
条件与无条件跳转通过修改 %rip/%eip 实现。条件跳转依据标志寄存器(由 cmp/test 等设置)。
无条件跳转
jmp label # 跳转到标号
jmp *%rax # 间接跳转(跳到 rax 中的地址)
jmp *(%rax) # 跳到内存中保存的地址(函数指针)
jmp/jcc 是相对跳转:机器码里存的是相对下一条指令的偏移(±128 或 ±2GB)。目标超过 ±2GB(跨模块跳转)必须用间接跳转(jmp *%rax)。
条件跳转(基于 cmp/test 结果)
下表假设先执行 cmp b, a(即计算 a - b):
| 指令 | 条件 | 语义 |
|---|---|---|
je / jz |
ZF=1 | 相等 / 为零 |
jne / jnz |
ZF=0 | 不等 / 非零 |
jg / jnle |
ZF=0 且 SF=OF | 有符号 a > b |
jge / jnl |
SF=OF | 有符号 a >= b |
jl / jnge |
SF≠OF | 有符号 a < b |
jle / jng |
ZF=1 或 SF≠OF | 有符号 a <= b |
ja / jnbe |
CF=0 且 ZF=0 | 无符号 a > b |
jae / jnb |
CF=0 | 无符号 a >= b |
jb / jnae |
CF=1 | 无符号 a < b |
jbe / jna |
CF=1 或 ZF=1 | 无符号 a <= b |
js |
SF=1 | 结果为负 |
jns |
SF=0 | 结果非负 |
jo / jno |
OF | 有符号溢出 / 未溢出 |
jc / jnc |
CF | 进位 / 无进位 |
记忆:g=greater, l=less, e=equal, a=above(无符号), b=below(无符号), n=not。 关键:
cmp b, a计算a - b。若想"a 大于 b 就跳",用jg;若想"b 大于 a 就跳",用jl。
条件设置(setcc)
setcc dst:条件为真时 dst = 1,否则 dst = 0(只写一个字节,通常配 %al 或内存)。后缀与 jcc 完全对应:sete/setne/setg/setge/setl/setle/seta/setb/setc/seto 等。
cmpl %ebx, %eax
setg %al # al = (eax > ebx) ? 1 : 0
movzbl %al, %eax # 零扩展为完整整数
循环结构
常用 loop 指令(以 %ecx/%rcx 为计数器,自动减 1 并在非零时跳转):
movl $5, %ecx
loop1:
# 循环体
loop loop1 # rcx--; if rcx != 0 jmp loop1
loop是微码实现,现代 CPU 上较慢,且依赖%rcx。手写dec+jne更灵活、更快,也更推荐:
movl $5, %ecx
loop2:
# 循环体
decl %ecx
jnz loop2
if / else 示例
cmpl %ebx, %eax # 计算 eax - ebx
jle else_branch # if eax <= ebx 跳转
# then: eax > ebx
jmp end
else_branch:
# else
end:
条件传送(避免分支)
movl %eax, %edx
cmpl %ebx, %eax
cmovg %ebx, %edx # if eax > ebx: edx = ebx (结果 edx = min(eax,ebx))
cmovcc 后缀与 jcc 对应(cmove/cmovne/cmovg/cmovl/cmova/cmovb/...),两个操作数都必须是寄存器。相比分支跳转,cmov 不预测失败、流水线不打断,代价是两侧都要求值(副作用都会被计算)。
Switch / 跳转表
利用变址寻址构造跳转表(地址数组),jmp *table(,%index,8) 实现多路分支:
# long dispatch(long idx); rdi = idx
dispatch:
cmpq $2, %rdi # 越界检查(table 共 2 项)
ja 3f
jmp *table(,%rdi,8) # 间接跳转:table[idx]
1: movq $100, %rax # case 0
ret
2: movq $200, %rax # case 1
ret
3: movq $-1, %rax # 默认
ret
.section .rodata
table: .quad 1b, 2b # 跳转地址表