四則演算とスタック
四則演算とスタックについて見ていきます。
.586
.model flat, stdcall
include iolib.inc
includelib iolib.lib
include kernel32.inc
includelib kernel32.lib
.data
kekka dd 0
STR0 db "13 + 35は、", 0
STR1 db "54 - 37は、", 0
STR2 db "7 * 8は、", 0
STR3 db "42 / 7は、", 0
.code
_start:
invoke OutputString, NEAR32 PTR STR0
mov eax, 13
push eax
mov edx, 35
push edx
pop edx
pop eax
add eax, edx
push eax
pop eax
mov kekka, eax
invoke OutputInteger, kekka
invoke OutputLetter, 13
invoke OutputLetter, 10
invoke OutputString, NEAR32 PTR STR1
mov eax, 54
push eax
mov edx, 37
push edx
pop edx
pop eax
sub eax, edx
push eax
pop eax
mov kekka, eax
invoke OutputInteger, kekka
invoke OutputLetter, 13
invoke OutputLetter, 10
invoke OutputString, NEAR32 PTR STR2
mov eax, 7
mov edx, 8
push eax
push edx
pop edx
pop eax
mul edx
push eax
pop eax
mov kekka, eax
invoke OutputInteger, kekka
invoke OutputLetter, 13
invoke OutputLetter, 10
invoke OutputString, NEAR32 PTR STR3
push 42
push 7
pop ebx
pop eax
mov edx, 0
div ebx
push eax
pop eax
mov kekka, eax
invoke OutputInteger, kekka
invoke OutputLetter, 13
invoke OutputLetter, 10
invoke ExitProcess, 0
end _start
少し見にくいアセンブリですが、一つ一つ解釈していきます。
mov
movはmoveの略で、値の代入に使います(moveの直訳は移動ですが、アセンブリではコピーと同じような意味です)。
mov (代入先),(代入するもの)
例えば、
mov eax, 13
では、eaxに13を代入します。C言語風に書き直すと、
eax=13;
となります。
レジスタとスタック
レジスタとは、一時的なデータ領域のことで、多くの種類があります。本稿では、以下の4つを用います。
eaxebxecxedx
このうち、eaxはWin32APIの戻り値などによく使われますが、それについては後述します。
「あれ、eaxやedxは.dataで宣言していないじゃないか」という方は鋭い。実は、レジスタは宣言が不要なのです。CPUが直接持っているようなものです。レジスタへの代入にはmovも使われますが、スタックもよく使われます。
「スタック」は、後入れ先出し(最後に入れたものが最初に出てくる)のデータ構造で、皿を積み重ねるイメージに近いです。皿に皿を重ねること(データの格納)を「push」、一番上の皿を取ること(データの取り出し)を「pop」と言います。

popでは常に一番上のものが取り出されるので、一番上に何があるか把握しておく必要があります。これは非常に大事なので覚えておいてください。
さて、これを何に使うかと言うと、一部の変数はスタックを通さないとmovできないものがあります。そして、これから説明する四則演算もその一つです。
四則演算
足し算、引き算は極めて覚えやすく、次のとおりです。movに似ているかと思います。
add tasareru, tasu
tasareru = tasareru + tasu;
sub hikareru, hiku
hikareru = hikareru - hiku;
なぜこういうのにスタックが必要なのかと言うと、C言語風のものをよく見てください。足される(引かれる)数が入っている変数に計算結果が入ります。これでは、足される(引かれる)数の元の値が分からなくなります。そこで、スタックを使います。
たとえば前述のコードでは、足し算を次のように行っています。
mov eax, 13 ; レジスタに13を代入
push eax ; 13の入ったレジスタをpush
; スタックの中身 …… [13]
mov edx, 35 ; レジスタに35を代入
push edx ; 35の入ったレジスタをpush
; スタックの中身 …… [35] [13]
pop edx ; popする
; スタックの一番上には35が入っているのでedxには35が入る
; スタックの中身 …… [13]
pop eax ; eaxにスタックの一番上=13が入る
add eax, edx ; popしたスタック同士で足し算
push eax ; 結果(eax)をもう一回push
pop eax ; そして、popします
mov kekka, eax ; 結果を格納する変数にeaxを入れる
スタックを使うと、足される(引かれる)数を忘れる事なく残しておく事ができます。もちろん、それらを残しておく必要がなければ、スタックを使う必要はありません。しかし、スタックを使う癖をつけておくと、長いプログラムが正しく動かない時のデバッグに助かります。
掛け算、割り算は少し難しくなります。
mul edx
eax = eax * edx;
div ebx
eax = eax / ebx; edx = eax % ebx;
スタックのeaxに依存しています。しかも、割り算では余りをedxに格納しています。そのためeaxは必須です。ちなみに、割り算の「A % B」は、「AからBを割った余り」を表しています。
また、割り算をする時、レジスタのedxを初期化しなければいけません。さもないと、保護エラーが発生します。筆者は、これに何回も泣かされました。
まとめ
今回は、アセンブリの文法からWin32APIの呼び出し、スタックについて説明しました。この他にも条件分岐などがありますが、少し複雑になりますのでアセンブリの雰囲気に慣れてからにします。スタックの性質は、コンピュータについて深く掘り下げている高校ではテストにも出るようです。
筆者は、まだ作文が苦手ですので、分からないところがあれば遠慮なく筆者のHPからメールでお問い合わせください。
次回予告
次回は、いよいよ「なでしこ」に触れ、コンパイラの制作に取り掛かります。もし簡単な構文の説明が早く終わった場合は、コンパイラに必要な要素「コマンドライン」についても言及する予定です。
