控制流(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          # 跳转地址表