控制流指令
控制流指令用于改变程序的执行顺序,包括无条件跳转、条件跳转、循环等。本章详细介绍 x86 的控制流指令。
无条件跳转
JMP 指令
JMP 指令无条件跳转到指定地址:
; 短跳转(相对跳转,-128 到 +127 字节)
jmp short label
; 近跳转(相对跳转,±2GB 范围)
jmp near label
; 直接跳转
jmp label
; 间接跳转(通过寄存器)
jmp eax ; 32 位模式
jmp rax ; 64 位模式
; 间接跳转(通过内存)
jmp [eax] ; 32 位模式
jmp [rax] ; 64 位模式
jmp [jump_table + eax*4] ; 跳转表
跳转示例
jmp skip ; 跳过下一条指令
mov eax, 0 ; 这条指令不会执行
skip:
mov eax, 1 ; 从这里继续执行
条件跳转
条件跳转根据标志位状态决定是否跳转。
零标志相关
| 指令 | 条件 | 描述 |
|---|---|---|
| JZ/JE | ZF = 1 | 等于/零时跳转 |
| JNZ/JNE | ZF = 0 | 不等于/非零时跳转 |
cmp eax, ebx
je equal ; 如果 EAX == EBX,跳转
jne not_equal ; 如果 EAX != EBX,跳转
test eax, eax
jz is_zero ; 如果 EAX == 0,跳转
jnz not_zero ; 如果 EAX != 0,跳转
符号标志相关
| 指令 | 条件 | 描述 |
|---|---|---|
| JS | SF = 1 | 为负时跳转 |
| JNS | SF = 0 | 为正时跳转 |
test eax, eax
js negative ; 如果 EAX < 0,跳转
jns positive ; 如果 EAX >= 0,跳转
进位标志相关
| 指令 | 条件 | 描述 |
|---|---|---|
| JC | CF = 1 | 有进位时跳转 |
| JNC | CF = 0 | 无进位时跳转 |
add eax, ebx
jc overflow ; 如果有进位,跳转
shl eax, 1
jc bit_out ; 如果移出的位是 1,跳转
溢出标志相关
| 指令 | 条件 | 描述 |
|---|---|---|
| JO | OF = 1 | 有溢出时跳转 |
| JNO | OF = 0 | 无溢出时跳转 |
add eax, ebx
jo signed_overflow ; 如果有符号溢出,跳转
无符号比较
| 指令 | 条件 | 描述 |
|---|---|---|
| JA/JNBE | CF=0 且 ZF=0 | 大于时跳转 |
| JAE/JNB/JNC | CF = 0 | 大于等于时跳转 |
| JB/JNAE/JC | CF = 1 | 小于时跳转 |
| JBE/JNA | CF=1 或 ZF=1 | 小于等于时跳转 |
cmp eax, ebx
ja above ; 如果 EAX > EBX(无符号),跳转
jae above_equal ; 如果 EAX >= EBX(无符号),跳转
jb below ; 如果 EAX < EBX(无符号),跳转
jbe below_equal ; 如果 EAX <= EBX(无符号),跳转
有符号比较
| 指令 | 条件 | 描述 |
|---|---|---|
| JG/JNLE | SF=OF 且 ZF=0 | 大于时跳转 |
| JGE/JNL | SF = OF | 大于等于时跳转 |
| JL/JNGE | SF ≠ OF | 小于时跳转 |
| JLE/JNG | SF≠OF 或 ZF=1 | 小于等于时跳转 |
cmp eax, ebx
jg greater ; 如果 EAX > EBX(有符号),跳转
jge greater_equal ; 如果 EAX >= EBX(有符号),跳转
jl less ; 如果 EAX < EBX(有符号),跳转
jle less_equal ; 如果 EAX <= EBX(有符号),跳转
条件跳转示例
找出最大值
; 找出 EAX 和 EBX 中的较大值
cmp eax, ebx
jge eax_greater
mov eax, ebx ; EBX 更大
eax_greater:
; EAX 中是较大值
绝对值
; 计算 EAX 的绝对值
cdq ; 符号扩展到 EDX
xor eax, edx ; 如果为负,取反
sub eax, edx ; 如果为负,加 1
条件设置指令
SETcc 指令根据条件设置字节为 0 或 1:
setz al ; 如果 ZF=1,AL=1;否则 AL=0
setnz al ; 如果 ZF=0,AL=1
setl al ; 如果有符号小于,AL=1
setg al ; 如果有符号大于,AL=1
setb al ; 如果无符号小于,AL=1
seta al ; 如果无符号大于,AL=1
; 示例:将比较结果转为布尔值
cmp eax, ebx
setl al ; AL = (EAX < EBX) ? 1 : 0
movzx eax, al ; EAX = (EAX < EBX) ? 1 : 0
循环指令
LOOP 指令
LOOP 指令使用 ECX/RCX 作为计数器:
; 基本循环
mov ecx, 10 ; 循环 10 次
loop_start:
; 循环体
loop loop_start ; ECX--,如果 ECX != 0,跳转
; 等价的普通跳转
mov ecx, 10
loop_start:
; 循环体
dec ecx
jnz loop_start
LOOP 变体
LOOPE/LOOPZ - 相等/零时循环
; 当 ECX != 0 且 ZF = 1 时继续循环
mov ecx, 100
mov esi, buffer
search_loop:
cmp byte [esi], 0
loope search_loop ; 继续循环直到找到非零字节或计数结束
LOOPNE/LOOPNZ - 不相等/非零时循环
; 当 ECX != 0 且 ZF = 0 时继续循环
mov ecx, 100
mov esi, buffer
search_loop:
cmp byte [esi], 'A'
loopne search_loop ; 继续循环直到找到 'A' 或计数结束
循环示例
数组求和
; 计算 32 位数组元素之和
; 输入:RSI = 数组地址,RCX = 元素个数
; 输出:EAX = 总和
array_sum:
xor eax, eax
sum_loop:
add eax, [rsi]
add rsi, 4
loop sum_loop
ret
字符串长度
; 计算以 null 结尾的字符串长度
; 输入:RSI = 字符串地址
; 输出:RCX = 长度
strlen:
xor ecx, ecx
strlen_loop:
cmp byte [rsi + rcx], 0
je strlen_done
inc ecx
jmp strlen_loop
strlen_done:
ret
冒泡排序
; 冒泡排序(升序)
; 输入:RSI = 数组地址,RCX = 元素个数
bubble_sort:
dec ecx ; 外循环次数 = n - 1
outer:
push rcx
mov rdi, rsi
inner:
mov eax, [rdi]
mov edx, [rdi + 4]
cmp eax, edx
jle no_swap
mov [rdi], edx
mov [rdi + 4], eax
no_swap:
add rdi, 4
loop inner
pop rcx
loop outer
ret
条件传送指令
CMOVcc 指令根据条件传送数据,避免分支预测失败:
; 条件传送
cmove eax, ebx ; 如果 ZF=1,EAX = EBX
cmovne eax, ebx ; 如果 ZF=0,EAX = EBX
cmovg eax, ebx ; 如果有符号大于,EAX = EBX
cmovl eax, ebx ; 如果有符号小于,EAX = EBX
cmova eax, ebx ; 如果无符号大于,EAX = EBX
cmovb eax, ebx ; 如果无符号小于,EAX = EBX
; 示例:取最大值(无分支)
cmp eax, ebx
cmovl eax, ebx ; 如果 EAX < EBX,EAX = EBX
条件传送示例
计算绝对值
; 无分支绝对值
mov edx, eax
neg edx ; EDX = -EAX
test eax, eax
cmovs eax, edx ; 如果 EAX < 0,EAX = -EAX
条件选择
; 根据条件选择值
cmp eax, 0
mov ebx, 100
mov edx, 200
cmovg ebx, edx ; 如果 EAX > 0,EBX = 200
分支预测优化
分支预测的影响
现代 CPU 使用分支预测器来预测跳转方向。预测失败会导致流水线清空,影响性能。
优化技巧
使用条件传送替代分支
; 传统方式(有分支)
cmp eax, ebx
jge skip
mov eax, ebx
skip:
; 优化方式(无分支)
cmp eax, ebx
cmovl eax, ebx
组织代码使常见路径顺序执行
; 假设错误情况很少见
test eax, eax
jz error ; 错误情况跳转
; 正常处理(顺序执行)
; ...
jmp done
error:
; 错误处理
done:
使用算术运算替代分支
; 传统方式:计算绝对值
test eax, eax
jns positive
neg eax
positive:
; 算术方式:无分支
cdq ; EDX = EAX 的符号位
xor eax, edx ; 如果为负,取反
sub eax, edx ; 如果为负,加 1
跳转表
跳转表用于实现多路分支(类似 switch 语句):
section .data
jump_table:
dq case_0
dq case_1
dq case_2
dq case_3
dq default
section .text
; switch (EAX)
cmp eax, 4
ja default
mov rdx, [jump_table + rax*8]
jmp rdx
case_0:
; 处理 case 0
jmp switch_end
case_1:
; 处理 case 1
jmp switch_end
case_2:
; 处理 case 2
jmp switch_end
case_3:
; 处理 case 3
jmp switch_end
default:
; 默认处理
switch_end:
小结
本章介绍了 x86 的控制流指令:
- 无条件跳转:JMP 指令的各种形式
- 条件跳转:根据标志位跳转的各类指令
- 条件设置:SETcc 指令
- 循环指令:LOOP、LOOPE、LOOPNE
- 条件传送:CMOVcc 指令
- 分支优化:减少分支预测失败的技巧
- 跳转表:实现多路分支
下一章将学习过程调用和栈帧管理。