アンティーク・アセンブラ~Antique Assembler

第3回 もしも“if”なら

この記事を読むのに必要な時間:およそ 10.5 分

条件分岐

一般的な CPU アーキテクチャは,現在実行中の命令が格納されているアドレスを保持するレジスタを持っています。

このレジスタは通常プログラムカウンタ(program counter)と呼ばれます。32ビットIntel 80x86アーキテクチャの場合はEIP(Extended Instruction Pointer)レジスタが相当します(⁠⁠extended⁠は,16ビット幅しか扱えなかった80286以前のIPレジスタに対して,32ビット化拡張がされている,という意味です⁠⁠。

EIPレジスタは,命令の実行ごとに次の命令位置,次の命令位置へと順次書き換えられますが,この「次に実行する命令の位置」を強制的に変化させる命令を制御転送(Controll Transfer)命令と呼びます。

制御転送命令には幾つか種類がありますが,通常のプログラムで使用するものは,手続き呼び出し(Procedure Call)に関する命令と,それ以外の分岐(Branch)に関する命令に大別できます。

※)
 連載次々回で説明予定

特に分岐命令には,制御転送の実施の有無を状態フラグの状況に応じて変化させる条件分岐(Conditional Branch)命令と,常に分岐を実施する無条件分岐命令があります。

Intel 80x86アーキテクチャの条件分岐命令はJccという形式で表記され,cc 部分には分岐を実施する際の条件名称が指定されます(無条件分岐命令はJMPと記述します⁠⁠。

主要な条件名称を以下に示します。

 フラグ状態欄は,C言語的な表記を使用します。

表1 条件名称

名称条件フラグ状態
e
Equal)
等しいZF
ne
Not Equal)
等しくない!ZF
g
Grater)
比較値より大きい!ZF && (SF == OF)
le
Less or Equal)
比較値以下ZF || (SF != OF)
ge
Grater or Equal)
比較値以上SF == OF
l
Less)
比較値未満SF != OF
a
Above)
(符号無し)比較値より大きい!CF && !ZF
na
Not Above)
(符号無し)比較値以下CF || ZF
cc
Carry Clear)
(符号無し)比較値以上!CF
cs
Carry Set)
(符号無し)比較値未満CF

「フラグ状態」は,⁠条件」欄における大小関係が成立する際に,⁠検証対象の値」から「比較値」減算した際のフラグ状態を示しています。

このような命名は,後述するcmpを使用した2値の比較の際の,ソース可読性に対する配慮に由来するものです。

条件分岐命令に先立って実行される命令は,必ずしも減算とは限りませんので,この「条件」はあくまで目安程度に考えてください。

符号無し数の比較や,⁠等しい」⁠等しくない」を理解するのは比較的簡単だと思いますが,符号付き数の比較に関するSF/OFの条件は少々面倒かもしれません。

“SF == OF⁠が成立するのは

  • 減算結果が 0 ないし正の値(SF == 0 && OF == 0)
  • 演算結果が符号付き数の上限超え(SF == 1 && OF == 1)

のいずれかの場合です(⁠⁠上限超え」と判断する理由は,SFでの説明をご覧ください⁠⁠。

減算の際に後者の条件が成立するのは,⁠検証対象の値」が正の値で,かつ「比較値」が負の値の場合に限定されますから,以上を総合すると,⁠SF == OF⁠ということは「検証対象の値」「比較値」ということに他ならないことがわかります(⁠⁠SF != OF⁠ならその逆⁠⁠。

なお,Intel 80x86アーキテクチャの状態フラグは,加減算のような(整数)演算命令の実行以外にも,比較(compare)命令CMPによって更新されます。

結果をレジスタなりメモリに格納する演算命令と違い,比較命令は状態フラグ更新以外の改変副作用が無いため,条件分岐命令のための状態フラグ更新には,比較命令を使用するケースの方が一般的と思われます。

リスト5 比較命令の挙動

    .text
    .align  4

    .global entry_point
entry_point:
    int3        # プログラム実行の一時停止

    movl    $0, %eax

    # 演算結果は 0 - 1 ⇒ 0xffffffff(-1)
    # 演算結果に応じて EFLAGS を更新
    # eax の値を - 1 で更新
    subl    $1, %eax

    # 演算結果は 0xffffffff - 0xffffffff ⇒ 0
    # 演算結果に応じて EFLAGS を更新
    # eax は 0xffffffff のまま
    cmpl    $0xffffffff, %eax

    .global end_of_program
end_of_program:
    int3        # プログラム実行の一時停止
    nop

図5 比較命令の挙動確認

(gdb) disassemble entry_point
Dump of assembler code for function entry_point:
0x00401000 <entry_point+0>:    int3   
0x00401001 <entry_point+1>:    mov    $0x0,%eax
0x00401006 <entry_point+6>:    sub    $0x1,%eax
0x00401009 <entry_point+9>:    cmp    $0xffffffff,%eax
End of assembler dump.
(gdb) run
....
0x00401001 in entry_point ()
(gdb) stepi
0x00401006 in entry_point ()
(gdb) info register eax eflags
eax            0x0    0
eflags         0x246    [ PF ZF IF ]
(gdb) stepi
0x00401009 in entry_point ()
    ※ "sub $0x01, %eax" の実行
(gdb) info register eax eflags
eax            0xffffffff    -1 ※ 値は更新
eflags         0x297    [ CF PF AF SF IF ]
(gdb) stepi
0x0040100c in end_of_program ()
    ※ "cmp $0xffffffff, %eax" の実行
(gdb) info register eax eflags
eax            0xffffffff    -1 ※ 値はそのまま
eflags         0x246    [ PF ZF IF ] ※ 演算結果による更新
(gdb) 

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。