コンテンツにスキップ

X86アセンブラ/算術演算命令

出典: フリー教科書『ウィキブックス(Wikibooks)』

算術演算命令

[編集]

算術演算命令は、 2 つのオペランドを取る。デスティネーション (転送先) とソース (転送元) である。 デスティネーションは、レジスターか、メモリーでの位置でなくてはならない。 ソースは、メモリーでの位置、レジスター、定数のどれかでなくてはならない。 二つのうち少なくとも一つは、レジスターでなくてはならない。 操作は、ソースとデスティネーションの両方を、メモリーでの位置にすることはできないためである。


add src, dest GAS文法
add dest, src MASM文法

この命令は、srcdestに加算する。 計算結果は、destに格納される。

つまり、MASM や NASM の文法を使う場合には、結果は最初の引数に格納される。 GAS の文法を使う場合には、結果は 2 番目の引数に格納される。


sub src, dest GAS文法
sub dest, src MASM文法

この命令は ADD と同じようであるが、デスティネーションからソースを減算する。

mul arg

この命令は「arg」に A レジスターのバイト長に応じた値を乗算する。 下表を参照のこと。

オペランドのサイズ 1 バイト 2 バイト 4 バイト
その他のオペランド AL AX EAX
結果の上位部分の格納先 AH DX EDX
結果の下位部分の格納先 AL AX EAX

2 番目の場合、古いプロセッサー向けに書かれたコードとの後方互換性のため、ターゲットは EAX ではない。


imul arg

MUL と同じであるが、符号付き数値のみを扱う。


div arg

この命令は、レジスターの内容を「arg」で除算する。 下表を参照のこと。

除数のサイズ 1 バイト 2 バイト 4 バイト
被除数 AX DX:AX EDX:EAX
剰余の格納先 AH DX EDX
商の格納先 AL AX EAX

%eax が設定されていれば、「cltd (MASM の文法では CDQ)」を使うことで、%edx を用意できる。

 cltd
 idiv   %ebx, %eax

商が商の格納先レジスターに合わなかった場合、数値オーバーフロー例外が発生する。 操作の後、全てのフラグは未定義の状態になる。

idiv arg

DIV と同じであるが、符号付き数値のみを扱う。

neg arg

引数の符号を算術的に反転させる。つまり 2 の補数を取る。

コード例

[編集]

実際に、C言語のプログラムをGCCでアセンブリコードに変換してみて、どのような命令が使われているかを学びましょう。

add.c
int add(int a, int b) {
    return a + b;
}

-S でアセンブリコードに変換すると、下記のようになります。

アセンブリコードに変換後
        .file   "add.c"
        .text
        .p2align 4
        .globl  add
        .type   add, @function
add:
.LFB0:
        .cfi_startproc
        leal    (%rdi,%rsi), %eax
        ret
        .cfi_endproc
.LFE0:
        .size   add, .-add
        .ident  "GCC: (FreeBSD Ports Collection) 12.0.0 20211010 (experimental)"
        .section        .note.GNU-stack,"",@progbits
加算を行っている部分
        leal    (%rdi,%rsi), %eax
関数の引数はレジスタで渡され、第一引数はDIレジスターに第二引数はSIレジスター渡されます。
関数の戻り地はEAXレジスターで返されます。
LEAL命令は、Load Effective Address Long で、第一オペランドで指定された「アドレス」を第二オペランドに代入します。
(%rdi,%rsi)はSIレジスターにDIレジスター分オフセットしたアドレスの値を参照しますので、EAXレジスターにはSIレジスターの値にDIレジスターの値を足した値が入ります。
AMD64にはそのものズバリのADD命令があるのですが、LEA命令にはADD命令とは別の演算器(アドレス演算器)が使われ、2つの加算器は並行して動作が可能なのでADD命令に使う演算器を加算以外のより高度な演算に開けるためにLEA命令を生成しています。

以下、補足

[編集]

キャリーあり算術演算命令

[編集]
adc src, dest GAS文法
adc dest, src MASM文法

キャリーあり加算。 destsrc + キャリーフラグを加算し、結果をdestに格納する。 通常、加算命令でレジスターの 2 倍の長さの値を扱うのを助ける。 以下の例では、sourceは 64 ビットの数値であり、destinationに加算される。

mov eax, [source] ; read low 32 bits
mov edx, [source+4] ; read high 32 bits
add [destination], eax ; add low 32 bits
adc [destination+4], edx ; add high 32 bits, plus carry


sbb src, dest GAS文法
sbb dest, src MASM文法

ボローあり減算。 src + キャリーフラグdestから減算し、結果をdestに格納する。 通常、減算命令でレジスターの 2 倍の長さの値を扱うのを助ける。

インクリメントとデクリメント

[編集]

inc arg

引数のレジスターの値を 1 ずつインクリメントする。 ADD arg, 1よりずっと早く動作する。

dec arg

引数のレジスターの値を 1 ずつデクリメントする。

ポインターの算術演算

[編集]

lea 命令は、算術演算にも使うことができ、特にポインターの算術演算に使われる。 アドレス計算命令を参照のこと。