← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

徹底理解 x86-64 旗標與條件分支

CMP 與 TEST、有號與無號條件,以及無分支程式碼的取捨

INTERACTIVE READING MODE →

徹底理解 x86-64 旗標與條件分支

本部落格文章以教學為目的。 範例採用 Intel 語法;效能必須在目標 CPU 與真實輸入分布上測量。

cmp eax, edx 沒有儲存結果,後面的 jl 為何知道誰比較小?同一組位元 0xFFFFFFFF 為何有時是 -1,有時是 4,294,967,295?關鍵在 RFLAGS,以及消費旗標時採用 signed 還是 unsigned 條件。

常見狀態旗標

旗標意義
ZF結果為 0
CFunsigned carry/borrow
SF結果最高位元
OFsigned 結果超出範圍
PF結果低位元組 parity

一般條件分支主要組合 ZF、CF、SF、OF。

cmp 與 test

cmp rax, rdx

概念上計算 rax-rdx,丟棄數值,只更新旗標。

cmp eax, 10
je equal_ten
jl signed_less_than_ten

分支並不是文法上綁定最近的 cmp。中間若有會改旗標的指令,分支就讀取新的旗標。

cmp eax, 10
add ecx, 1
jl somewhere      ; 讀取 add 的旗標

test 像丟棄結果的 and,常用於零值與 bit mask 檢查。

test rax, rax
jz is_zero
test eax, 1
jnz is_odd

相等與大小關係

相等不分 signedness:je/jz 看 ZF=1,jne/jnz 看 ZF=0。大小關係則必須區分。

意義signedunsigned
left < rightjljb
left <= rightjlejbe
left > rightjgja
left >= rightjgejae
mov eax, 0FFFFFFFFh
cmp eax, 1
jl signed_less       ; -1 < 1,成立
jb unsigned_below    ; 4294967295 < 1,不成立

暫存器沒有 signed 型別標籤;差別在於如何解讀同一組比較旗標。

CF 與 OF 是不同溢位

8 位元 0xFF+1=0x00 有 unsigned carry,但 signed 的 -1+1 正常。0x7F+1=0x80 對 unsigned 是 128,對 signed 則超出 127。

add al, 1
jc unsigned_overflow
jo signed_overflow

C/C++ unsigned 算術定義為 modulo;signed overflow 是未定義行為。CPU 提供 OF 並不會替語言定義它。

inc、dec 保留 CF,所以和 add 1、sub 1 並非所有旗標都相同。

setcc 與 cmovcc

xor eax, eax
cmp edi, esi
setl al
ret

把 signed edi < esi 轉成 0 或 1。setcc 只寫一個位元組,因此要明確清理高位。

cmp eax, edx
cmovl eax, edx

條件搬移不分割控制流程,但可能拉長 dependency chain,並不保證比 branch 快。

branchless 不等於自動更快

現代 CPU 會預測分支。高度可預測的 jcc 很便宜;輸入不規則且兩條路徑都很短時,setcc、cmovcc、mask 可能有利。必須根據真實分支分布、misprediction、兩側工作量、增加的指令與依賴、編譯器輸出,在目標 CPU 測量。

迴圈與短路條件

xor eax, eax
xor edx, edx
loop_test:
    cmp rdx, rsi
    jae loop_done
    add eax, [rdi + rdx*4]
    inc rdx
    jmp loop_test
loop_done:
    ret

jae 是 unsigned i >= count。最佳化可能用 rotation、unrolling、vectorization 改變形狀。

test rcx, rcx
jz done
cmp byte ptr [rcx + READY_OFFSET], 0
je done
call use
done:

這是 p != nullptr && p->ready 的可能形式。第一個分支避免 null dereference,順序本身就是安全契約。

jump table

sub eax, 3
cmp eax, 4
ja default_case
jmp qword ptr [table + rax*8]

先正規化 case,再以 unsigned 範圍檢查限制 table index,最後間接跳轉。範圍檢查也是重要的安全邊界。

函式呼叫不保存一般條件旗標

cmp eax, edx
call helper
jl old_result       ; helper 可能已改變旗標

需要結果時,應在呼叫前轉成 0/1,或呼叫後重新比較。

總結

cmp 與 test 不儲存計算結果,只建立旗標。相等看 ZF;大小必須區分 signed 的 SF/OF 與 unsigned 的 CF/ZF。不要只找最近的 cmp,而要追蹤最後定義旗標的指令、條件 signedness,以及每條控制邊上可能的輸入範圍。

官方資料