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

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

算術演算命令[編集]

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


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

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

つまり、MASN や 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コード
#include <stdio.h>

int main(void) {
	int kazuA = 2;
	int kazuB = 3;
	int kazuC = kazuA + kazuB;
	printf("kotae wa %d \n",kazuC);   // 「答えは 」のつもり
	return 0;
}

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

アセンブリコードに変換後
	.file	"keisan.c"
	.text
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "kotae wa %d \12\0"
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB13:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	call	___main
	movl	$2, 28(%esp)
	movl	$3, 24(%esp)
	movl	28(%esp), %edx
	movl	24(%esp), %eax
	addl	%edx, %eax
	movl	%eax, 20(%esp)
	movl	20(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$LC0, (%esp)
	call	_printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE13:
	.ident	"GCC: (MinGW.org GCC-8.2.0-5) 8.2.0"
	.def	_printf;	.scl	2;	.type	32;	.endef


movl $2, 28(%esp)

の丸カッコとその前の数字は、メモリ番号からオフセット(おそらく引き算)した位置をあらわす。

この場合、「2」という数値を、espレジスタで示した数値(32番)から28個オフセットした位置につまり(32-28=4)に代入しろ、という命令でしょう。

また、この2は定数なので、「$2」のようにプレフィックスとしてドル記号 $ をつけます。


また、理由はよく分からないが、スタック(push と pop)で確保している仕組みになっている。

※ 読者に理由の分かる人がいたら、編集を手伝ってください。



以下、補足[編集]

キャリーあり算術演算命令[編集]

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 命令は、算術演算にも使うことができ、特にポインターの算術演算に使われる。 アドレス計算命令を参照のこと。