跳到主要内容

控制流指令

控制流指令用于改变程序的执行顺序,包括无条件跳转、条件跳转、循环等。本章详细介绍 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/JEZF = 1等于/零时跳转
JNZ/JNEZF = 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,跳转

符号标志相关

指令条件描述
JSSF = 1为负时跳转
JNSSF = 0为正时跳转
test eax, eax
js negative ; 如果 EAX < 0,跳转
jns positive ; 如果 EAX >= 0,跳转

进位标志相关

指令条件描述
JCCF = 1有进位时跳转
JNCCF = 0无进位时跳转
add eax, ebx
jc overflow ; 如果有进位,跳转

shl eax, 1
jc bit_out ; 如果移出的位是 1,跳转

溢出标志相关

指令条件描述
JOOF = 1有溢出时跳转
JNOOF = 0无溢出时跳转
add eax, ebx
jo signed_overflow ; 如果有符号溢出,跳转

无符号比较

指令条件描述
JA/JNBECF=0 且 ZF=0大于时跳转
JAE/JNB/JNCCF = 0大于等于时跳转
JB/JNAE/JCCF = 1小于时跳转
JBE/JNACF=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/JNLESF=OF 且 ZF=0大于时跳转
JGE/JNLSF = OF大于等于时跳转
JL/JNGESF ≠ OF小于时跳转
JLE/JNGSF≠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 指令
  • 分支优化:减少分支预测失败的技巧
  • 跳转表:实现多路分支

下一章将学习过程调用和栈帧管理。