条件分岐とフラグレジスタ

AsmWalker ガイド — if 文・ループがアセンブラではどう動くか

1. フラグレジスタとは何か

CPU には通常の汎用レジスタ(rax, r0 など)とは別に、フラグレジスタ(APSR / RFLAGS)という 特殊なレジスタがあります。 演算命令や比較命令を実行するたびに、その結果の性質を示すビットが自動的にセットされます。

条件分岐命令はこのフラグを読んで「分岐するかどうか」を決定します。 つまり if (a > b) という C の条件は、アセンブラでは 「比較命令でフラグを立て → 条件分岐命令でフラグを見て分岐」という 2 ステップで実現されます。

AsmWalker での確認
特殊レジスタパネルの下部に Z / N / C / V のフラグ状態が表示されます。 CMP 命令を実行した後でフラグが変化することを確認してください。

2. 4 つのフラグ

Z
Zero
計算結果が 0
N
Negative
結果が負
C
Carry
桁あふれ(符号なし)
V
oVerflow
桁あふれ(符号あり)
フラグARM 呼称x86 呼称セットされる条件
Z(Zero) ZZF 命令の計算結果がちょうど 0
例: CMP a, b は内部で a - b を計算する。a と b が等しければ結果が 0 になり Z=1 がセットされる
N(Negative) NSF(Sign) 演算結果の最上位ビットが 1(負の数)
C(Carry) CCF 符号なし整数として桁あふれが発生
V(oVerflow) VOF 符号あり整数として桁あふれが発生

3. フラグを立てる命令:CMP と TEST

CMP — 大小比較

CMP a, ba - b を計算し、その結果によってフラグをセットします。 計算結果自体はどこにも保存されません(比較のためだけに使う減算命令です)。

ARM — CMP のフラグ

; CMP 3, 3  (a == b)
; → Z=1, N=0, C=1, V=0

; CMP 5, 3  (a > b)
; → Z=0, N=0, C=1, V=0

; CMP 3, 5  (a < b)
; → Z=0, N=1, C=0, V=0

ARM の C フラグは 「借りが発生しなかった」ときに 1
a >= b → C=1、a < b → C=0

x86-64 — CMP のフラグ

; CMP 3, 3  (a == b)
; → Z=1, N=0, C=0, V=0

; CMP 5, 3  (a > b)
; → Z=0, N=0, C=0, V=0

; CMP 3, 5  (a < b)
; → Z=0, N=1, C=1, V=0

x86 の CF は 「借りが発生した」ときに 1
a < b → CF=1、a >= b → CF=0

ARM と x86 で C フラグの意味が逆です
同じ CMP 3, 5(a < b)でも ARM は C=0、x86 は CF=1 となります。 AsmWalker でモードを切り替えながら CMP 命令のフラグ変化を比べてみてください。

TEST — ビットマスク確認

TEST a, ba AND b を計算してフラグをセットします(結果は保存しない)。 主に「あるビットが立っているか」を調べるために使われます。

; if (x != 0) の判定
test eax, eax     ; eax AND eax → eax が 0 なら Z=1
jnz  .nonzero     ; Z=0 なら分岐(eax ≠ 0)

; if (x & 1) の判定(奇数チェック)
test eax, 1       ; eax AND 1 → ビット0 が 0 なら Z=1
jz   .even        ; Z=1 なら偶数
AsmWalker での確認
CMP 命令のステップを実行した後、命令詳細パネルに flags: Z=0, N=0, C=0, V=0 のようなフラグ変化が表示されます。 次の条件分岐命令がそのフラグを参照して分岐するかどうかを決めていることを確認してください。

4. 条件分岐命令(Jcc / Bcc)

CMP でフラグが立ったあと、条件分岐命令がそのフラグを読んで「指定のラベルへジャンプするか否か」を判断します。 条件が成立しない場合は次の命令に進みます(fall-through)。

C の条件x86 命令ARM 命令チェックするフラグ
==(等しい)JE / JZBEQZ=1
!=(等しくない)JNE / JNZBNEZ=0
<(符号あり)JL / JNGEBLTN≠V
<=(符号あり)JLE / JNGBLEZ=1 または N≠V
>(符号あり)JG / JNLEBGTZ=0 かつ N=V
>=(符号あり)JGE / JNLBGEN=V
<(符号なし)JB / JNAEBCC / BLOC=0
>(符号なし)JA / JNBEBHIC=1 かつ Z=0
無条件分岐JMPB(常に成立)
命令名の読み方
x86 の J は Jump、E=Equal、N=Not、L=Less、G=Greater、 B=Below(符号なし)、A=Above(符号なし)です。 ARM の B は Branch、EQ=Equal、NE=Not Equal などのサフィックスが付きます。

5. if / else 文の翻訳パターン

GCC がどのように if/else を翻訳するかを見てみましょう。

// C言語
if (a > b) {
    result = a;
} else {
    result = b;
}

ARM — if/else の翻訳

    ldr  r0, [fp, #-8]    ; r0 = a
    ldr  r1, [fp, #-12]   ; r1 = b
    cmp  r0, r1            ; a - b でフラグをセット
    ble  .Lelse            ; a <= b なら else へ

; if の本体(a > b)
    ldr  r3, [fp, #-8]    ; result = a
    str  r3, [fp, #-16]
    b    .Lend             ; else をスキップ

.Lelse:
    ldr  r3, [fp, #-12]   ; result = b
    str  r3, [fp, #-16]

.Lend:

x86-64 — if/else の翻訳

    mov  eax, DWORD PTR [rbp-4]  ; eax = a
    cmp  eax, DWORD PTR [rbp-8]  ; a - b でフラグをセット
    jle  .Lelse                   ; a <= b なら else へ

; if の本体(a > b)
    mov  eax, DWORD PTR [rbp-4]
    mov  DWORD PTR [rbp-12], eax  ; result = a
    jmp  .Lend                    ; else をスキップ

.Lelse:
    mov  eax, DWORD PTR [rbp-8]
    mov  DWORD PTR [rbp-12], eax  ; result = b

.Lend:

GCC は C の条件を反転させた分岐命令を生成するのが基本パターンです。 if (a > b) なら「a > b でないとき(a <= b のとき)else へ飛ぶ」という 逆向きの条件でジャンプします。条件が成立するとき(true の場合)は次の命令へ続くだけです。

条件の「反転」に慣れる
C の if (cond) → アセンブラでは j[NOT cond] else_label が出てきます。 「else に飛ぶ条件」が書かれているので、最初は逆に読めてしまうことがあります。 「この分岐は else へのエスケープ」と考えると読みやすくなります。
AsmWalker での確認
条件分岐命令のステップで、アセンブラパネルのハイライトが次の行に進むか、 ラベル先にジャンプするかを確認してください。 命令詳細パネルには「Z=1 のため分岐しました」のような説明が表示されます。

6. while / for ループの翻訳パターン

ループは「条件チェック → 本体 → ループ先頭へ戻るジャンプ」の繰り返しに翻訳されます。

// C言語
int i = 0;
int sum = 0;
while (i < 5) {
    sum += i;
    i++;
}

ARM — while ループ

    mov  r3, #0
    str  r3, [fp, #-8]    ; i = 0
    mov  r3, #0
    str  r3, [fp, #-12]   ; sum = 0

.Lloop:
    ldr  r3, [fp, #-8]    ; r3 = i
    cmp  r3, #4            ; i - 4 でフラグ
    bgt  .Lend             ; i > 4 なら終了

    ldr  r2, [fp, #-12]
    ldr  r3, [fp, #-8]
    add  r3, r2, r3        ; sum += i
    str  r3, [fp, #-12]

    ldr  r3, [fp, #-8]
    add  r3, r3, #1        ; i++
    str  r3, [fp, #-8]

    b    .Lloop             ; 先頭に戻る
.Lend:

x86-64 — while ループ

    mov  DWORD PTR [rbp-4], 0   ; i = 0
    mov  DWORD PTR [rbp-8], 0   ; sum = 0

.Lloop:
    cmp  DWORD PTR [rbp-4], 4   ; i - 4 でフラグ
    jg   .Lend                  ; i > 4 なら終了

    mov  eax, DWORD PTR [rbp-4]
    add  DWORD PTR [rbp-8], eax ; sum += i

    add  DWORD PTR [rbp-4], 1   ; i++

    jmp  .Lloop                 ; 先頭に戻る
.Lend:

GCC の -O0 出力では「ループ先頭でチェックしてから本体を実行」するパターンが多いです。 最適化(-O1 以上)では「先に本体を実行してからチェック」する do-while 形式に 変換されることもあります(余分なジャンプを削減できるため)。

AsmWalker での確認
ステップを進めながら、ループ本体の命令が何度も繰り返されることを確認してください。 アセンブラパネルの同じ行が複数回ハイライトされます。 条件分岐命令で「今回は分岐しなかった」「今回は分岐した(ループを抜けた)」を フラグ値と合わせて観察してみてください。

7. 符号あり比較と符号なし比較

C の比較は変数の型によって「符号あり比較」か「符号なし比較」かが決まります。 アセンブラでは異なる条件分岐命令を使い分けます。

比較の種類x86 分岐命令ARM 分岐命令
int(符号あり)符号あり比較JL / JG / JLE / JGEBLT / BGT / BLE / BGE
unsigned int(符号なし)符号なし比較JB / JA / JBE / JAEBCC / BHI / BLS / BCS

これは CPU がフラグを使う方法の違いです。 符号あり比較では N フラグと V フラグの関係を使い、 符号なし比較では C フラグと Z フラグを使います。

なぜ使い分けが必要か
-1(符号あり)は 2 の補数表現で 0xFFFFFFFF です。 符号なしとして解釈すると約 42 億(= 4294967295)になります。 -1 < 1 は符号ありでは true ですが、符号なしでは false になります。 コンパイラは C の変数型から自動的に適切な比較命令を選択します。

8. 論理演算と短絡評価

&&|| を使った複合条件は、 アセンブラでは複数の CMP + 条件分岐の組み合わせに展開されます。

// C言語: if (a > 0 && b > 0)
    cmp  eax, 0
    jle  .Lfalse    ; a <= 0 なら即終了(短絡評価: a が false なら b を見ない)
    cmp  ebx, 0
    jle  .Lfalse    ; b <= 0 なら終了

; 両方 true の処理
    ...
.Lfalse:
    ...

C の論理 AND(&&)は左から順に評価し、最初に false になった時点で残りを評価しない 「短絡評価(short-circuit evaluation)」が保証されています。 アセンブラでは、左側の条件が偽のとき即座にラベルへジャンプすることで実現されています。

ARM — OR 条件(||)

    cmp  r0, #0
    bgt  .Ltrue    ; a > 0 なら即 true
    cmp  r1, #0
    ble  .Lfalse   ; b <= 0 なら false
.Ltrue:
    ; 少なくとも一方が true

x86-64 — OR 条件(||)

    cmp  eax, 0
    jg   .Ltrue    ; a > 0 なら即 true
    cmp  ebx, 0
    jle  .Lfalse   ; b <= 0 なら false
.Ltrue:
    ; 少なくとも一方が true

9. まとめ

概念内容
フラグレジスタZ / N / C / V の 4 ビット。演算・比較命令が自動更新
CMP a, ba - b を計算してフラグをセット(結果は保存しない)
TEST a, ba AND b を計算してフラグをセット(ビット検査に使う)
条件分岐命令フラグを読んで分岐するかを決定(Jcc / Bcc)
if/else の変換条件を反転した分岐で else ブロックへエスケープ
ループの変換先頭でチェック → 本体 → 先頭へ無条件ジャンプ
符号あり vs 符号なし変数型によって異なる分岐命令(JL/JG vs JB/JA)を使用
短絡評価&&/|| は複数の CMP + 分岐の連鎖で実現

条件分岐の仕組みを理解すると、アセンブラで書かれたコードの制御フローを 上から順に追うだけで「どの条件でどこへ飛んでいるか」が読み解けるようになります。 AsmWalker でサンプルコードをステップ実行しながら、 C の条件式とアセンブラの分岐命令を対比させてみてください。