3.9 跳转指令
跳转指令使程序能够改变指令的执行顺序,是实现分支和循环的基础。跳转指令分为无条件跳转和条件跳转。
无条件跳转:jmp
jmp 指令总是执行跳转,不受任何条件限制。
直接跳转
跳转目标由标号(label)指定,在汇编代码中以 .L 开头命名。
jmp .Ldone # 跳转到 .Ldone 标号处继续执行
.Ldone:
# 跳转到这里继续执行
ret间接跳转
跳转目标从寄存器或内存中读取,实现动态跳转。详见 。
jmp *%rax # 以 %rax 中的值作为目标地址跳转
jmp *(%rdi) # 从内存中读取目标地址并跳转
jmp *12(%rbp, %rdi, 8) # 从计算出的内存地址读取目标间接跳转的语法特征:* 前缀表示间接跳转。
条件跳转指令
条件跳转根据 的组合决定是否改变控制流。
有符号数条件跳转
| 指令 | 含义 | 条件 | C 比较 |
|---|---|---|---|
je (或 jz) | 相等时跳转 | ZF = 1 | a == b |
jne (或 jnz) | 不等时跳转 | ZF = 0 | a != b |
js | 负数时跳转 | SF = 1 | a < 0 |
jns | 非负时跳转 | SF = 0 | a >= 0 |
jl (或 jnge) | 小于时跳转 (less) | SF ≠ OF | a < b |
jge (或 jnl) | 大于等于时跳转 | SF = OF | a >= b |
jle (或 jng) | 小于等于时跳转 | (SF ≠ OF) | ZF | a <= b |
jg (或 jnle) | 大于时跳转 (greater) | (SF = OF) & ZF = 0 | a > b |
无符号数条件跳转
| 指令 | 含义 | 条件 |
|---|---|---|
jb (或 jnae, jc) | 低于时跳转 (below) | CF = 1 |
jae (或 jnb, jnc) | 高于等于时跳转 | CF = 0 |
jbe (或 jna) | 低于等于时跳转 | CF = 1 | ZF = 1 |
ja (或 jnbe) | 高于时跳转 (above) | CF = 0 & ZF = 0 |
条件跳转操作示例
# C 代码: if (a >= b) goto true_branch;
# 假设 %rdi = a, %rsi = b
cmpq %rsi, %rdi # 计算 a - b
jge .L_true # 如果 a >= b,跳转
# a < b 的情况继续执行
# ...
.L_true:
# a >= b 的情况
# ...条件跳转不能作为间接跳转
条件跳转不支持间接跳转(即不能 je *%rax),只有无条件跳转 jmp 支持间接跳转。
跳转指令的编码
大多数跳转指令使用 PC 相对寻址(PC-relative addressing),即目标地址编码为当前指令地址的偏移量。
# 汇编代码
je .Ltarget
# 机器码示例 (假设 Ltarget 在当前指令后 0x42 字节处)
# 0x7E 0x42
# 其中 0x7E = je 的 opcode,0x42 = 相对于下一条指令的偏移短跳转 vs 近跳转
- 短跳转(short jump):偏移在一个字节内(-128~127),2 字节指令
- 近跳转(near jump):偏移在四个字节内,6 字节指令
- 汇编器自动选择合适的编码格式
分支的基本模式
if-else 模式
# C 代码
# if (a >= b) {
# result = a - b;
# } else {
# result = a + b;
# }
# 汇编
cmpq %rsi, %rdi # 比较 a (%rdi) 和 b (%rsi)
jl .Lelse # 如果 a < b,跳转到 else
subq %rsi, %rdi # a >= b: result = a - b
movq %rdi, %rax
jmp .Ldone
.Lelse:
addq %rsi, %rdi # a < b: result = a + b
movq %rdi, %rax
.Ldone:
ret条件表达式模式
C 语言的三目运算符 ?: 通常编译为条件传送或条件跳转:
# C 代码: long abs = (x >= 0) ? x : -x;
# 使用条件跳转的实现
testq %rdi, %rdi
jge .Lpos
negq %rdi
.Lpos:
movq %rdi, %rax
retgoto 模式
C 语言的 goto 对应汇编中的无条件跳转:
// C 代码(使用 goto 辅助翻译)
long fact_goto(long n) {
long result = 1;
if (n <= 1) goto done;
loop:
result *= n;
n--;
if (n != 1) goto loop;
done:
return result;
}# 对应的汇编
fact_goto:
movq $1, %rax # result = 1
cmpq $1, %rdi # 比较 n 和 1
jle .Ldone # 如果 n <= 1,跳转到 done
.Lloop:
imulq %rdi, %rax # result *= n
subq $1, %rdi # n--
cmpq $1, %rdi # 比较 n 和 1
jne .Lloop # 如果 n != 1,继续循环
.Ldone:
ret条件跳转的性能考虑
现代 CPU 使用分支预测(branch prediction)来推测执行。预测错误会导致流水线冲刷(pipeline flush),造成性能损失。
- 预测正确:约 0-2 个周期损失
- 预测错误:约 15-20 个周期损失
为减少分支预测失败,编译器会尝试将简单分支转换为 。
跳转指令总结
| 类型 | 指令 | 格式 | 用途 |
|---|---|---|---|
| 无条件跳转 | jmp | jmp .Ltarget 或 jmp *%reg | 循环/switch/goto |
| 条件跳转 | je/jne/jl/jge/... | je .Ltarget | if-else/循环条件 |
| 条件跳转(无符号) | jb/jae/jbe/ja | ja .Ltarget | 无符号比较 |
相关笔记
-
- 跳转指令的判断依据
-
- 分支的完整实现
-
- 循环中跳转的模式
-
- 间接跳转的应用
链接到
- 上一个知识点:3.8 条件码
- 下一个知识点:3.10 条件分支
- 相关知识点:4.7 SEQ处理器、4.8 SEQ各阶段分析