乗算と除算
乗算や除算処理は、加算や減算とは少し異なった命令になっています。まずは、乗算命令MULから見てみましょう。
mul src
乗除算命令は、加減算命令とは異なり、ディスティネーション・オペランドはソース・オペランドによって決定されます。よって、命令文として指定することができるのはソース・オペランドのみです。ソース・オペランドにはレジスタまたはメモリアドレスを指定することができますが、即値を指定することはできません。この制限は、ソースオペランドのサイズによってディスティネーション・オペランドを決定しなければならないためです。
乗算の結果は、2つのレジスタに分散されて格納される仕組みになっています。例えば、ソース・オペランドが8ビットのレジスタやメモリ領域で指定された場合は、ALレジスタの値とソース・オペランドの値を乗算して、その結果を16ビットのAXレジスタに格納します。
| ソース・オペランドのサイズ | 演算 | 出力レジスタ |
| 8ビット | AL*ソースオペランド | AX |
| 16ビット | AX*ソースオペランド | DX:AX |
| 32ビット | EAX*ソースオペランド | EDX:EAX |
ソース・オペランドが16ビットの場合、AXレジスタの値とソース・オペランドが乗算され、その結果は32ビットとしてDXレジスタとAXレジスタに分割されます。DXレジスタには結果の上位16ビットが、AXレジスタには下位16ビットが格納されます。
ソース・オペランドが32ビットの場合も、16ビットの場合と動作は同じです。EAXレジスタの値とソース・オペランドが乗算され、その結果が64ビットとして、EDXレジスタに上位32ビット、EAXレジスタに下位32ビットが格納されます。
#include <stdio.h> int main() { int iEAX, iEDX, value = 5; __asm { mov eax, 10; mul value; mov iEAX, eax; mov iEDX, edx; } printf("edx=%d eax=%d\n", iEDX, iEAX); return 0; }
edx=0 eax=50
sample03は、MUL命令のソース・オペランドに32ビットであるint型の変数valueを指定しています。よって、このMUL命令では32ビットのディスティネーション・オペランドが利用されるということが自動的に決定され、EAXレジスタとvalueの値が乗算されます。そして、その結果は上位32ビットがEDXレジスタに、下位32ビットがEAXレジスタに格納されます。
上位ビットを保存するEDXレジスタは、計算結果が32ビットでは表現できない大きな値にならなければ意味がありません。MUL命令ではEAXレジスタにあらかじめロードしていた10と、value変数の値5を乗算しています。プログラムの実行結果を見ると、EAXレジスタに10*5の計算結果である50がストアされていたことが確認できます。この場合、上位32ビットを利用する必要は無いのでEDXレジスタの値は0になっています。
乗算結果がソース・オペランドのサイズでは表現できないような大きな値になってしまった場合は、上位ビットが利用されます。例えば、16ビットのソース・オペランドでMULを実行した場合で、結果が16ビットの範囲である65535以上になると32ビットの値として上位16ビットをDXレジスタに、下位16ビットをAXレジスタに保存します。
#include <stdio.h> int main() { short iAX, iDX, value = 1234; __asm { mov ax, 12345; mul value; mov iAX, ax; mov iDX, dx; } printf("dx=%d ax=%d\n", iDX, iAX); return 0; }
dx=232 ax=29378
sample04では、16ビットであるshort型の変数valueをMUL命令に与え、意図的に16ビット以上の結果になるよう計算させています。
12,345×1,234の結果は10進数で15,233,730となります。先ほどのインラインアセンブラ内で実行してAXレジスタとDXレジスタの値を調べると、上位16ビットを保存するDXレジスタの値が10進数で232、下位16ビットを保存するAXレジスタが10進数で29378となっています。15,233,730という大きな値は16ビットのレジスタ単体では表現できいないため、このように2つのレジスタに分散して保存されたのです。では、計算結果は正しく出力されたのでしょうか。
これらの数字を2進数に変換すると、DXレジスタの値は0000 0000 1110 1000、AXレジスタの値は0111 0010 1100 0010となります。これらの2進数の値を結合し32ビットの値としてみれば、計算結果が正しく出力されているかどうかを調べられます。1110 1000 0111 0010 1100 0010を10進数に変換すると、確かに15,233,730という値であることが確認できます。
除算や剰余を求めるにはDIV命令を使います。DIV命令もMUL命令と同様にソース・オペランドのみを設定し、ソース・オペランドのサイズから利用する被除数と結果を保存する出力先のレジスタが決定します。DIV命令は、ソース・オペランドにレジスタまたはメモリアドレスを指定します。やはり、即値を指定することはできません。演算結果は、ソース・オペランドのサイズに従って商と余りがレジスタに保存されます。
div src
例えば、ソース・オペランドが8ビットの場合、被除数は16ビットのAXレジスタが対象となります。AXレジスタの値がソース・オペランドの値で割られ、商がALレジスタに、余りがAHレジスタに保存されます。ソース・オペランドが16ビットの場合は、被除数が上位DX、下位AXの組み合わせによる32ビット値で計算され、商がAX、余りがDXレジスタに保存されます。
| ソース・オペランドのサイズ | 演算 | 商 | 余り |
| 8ビット | AX*ソースオペランド | AL | AH |
| 16ビット | DX:AX*ソースオペランド | AX | DX |
| 32ビット | EDX:EAX*ソースオペランド | EAX | EDX |
#include <stdio.h> #define DIVISOR 16 int main() { char value = 5, iAL, iAH; __asm { mov ax, DIVISOR; div value; mov iAL, al; mov iAH, ah; } printf("%d ÷ %d = %d, 余り = %d\n", DIVISOR, value, iAL, iAH); return 0; }
16 ÷ 5 = 3, 余り = 1
sample05は、AXレジスタに16をロードし、8ビットの変数valueをソース・オペランドにDIV命令を実行しています。これによって、AXレジスタの値からvalue変数の値で割った商がALレジスタに、余りがAHレジスタにストアされます。この結果をiAL変数とiAH変数にそれぞれ転送し、実行結果を表示しています。
インクリメント・デクリメント
レジスタまたはメモリの現在の値に1加算する、または現在の値から1減算するという処理は、ADD命令やSUB命令でも実現できますが、インクリメントを行う命令と、デクリメントを行う命令が特別に用意されています。インクリメントを行うにはINC命令を使い、デクリメントを行うにはDEC命令を使います。
inc dest
dec dest
INC命令もDEC命令も、destにディスティネーション・オペランドのみを指定します。これらの命令では、ディスティネーション・オペランドで指定したレジスタまたはメモリ領域の現在の値からインクリメントまたはデクリメントします。
#include <stdio.h> int main() { int i = 100, iEAX; __asm { mov eax, 0; INC eax; INC eax; INC eax; DEC i; DEC i; mov iEAX, eax; } printf("EAX=%d, i=%d\n", iEAX, i); return 0; }
EAX=3, i=98
sample06では、初期値0のEAXレジスタに対してINC命令を3回実行し、初期値100のi変数に対してDEC命令を2回実行しています。初期値0の値から3回インクリメントすると、INC命令を実行するごとにEAXレジスタの値が1ずつ増えていくので、EAXレジスタの値は3となっています。同様に、初期値100の値から2回デクリメントすると、DEC命令を実行するごとにi変数の値が1ずつ減っているのでi変数の値は98となります。
