SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

インラインアセンブラで学ぶアセンブリ言語

インラインアセンブラで学ぶアセンブリ言語 第2回

アセンブリ言語で算術演算/論理演算を行う


乗算と除算

 乗算や除算処理は、加算や減算とは少し異なった命令になっています。まずは、乗算命令MULから見てみましょう。

mul src

 乗除算命令は、加減算命令とは異なり、ディスティネーション・オペランドはソース・オペランドによって決定されます。よって、命令文として指定することができるのはソース・オペランドのみです。ソース・オペランドにはレジスタまたはメモリアドレスを指定することができますが、即値を指定することはできません。この制限は、ソースオペランドのサイズによってディスティネーション・オペランドを決定しなければならないためです。

 乗算の結果は、2つのレジスタに分散されて格納される仕組みになっています。例えば、ソース・オペランドが8ビットのレジスタやメモリ領域で指定された場合は、ALレジスタの値とソース・オペランドの値を乗算して、その結果を16ビットのAXレジスタに格納します。

表1 MUL命令の結果
ソース・オペランドのサイズ演算出力レジスタ
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ビットが格納されます。

sample03
#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レジスタに保存します。

sample04
#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型の変数valueMUL命令に与え、意図的に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レジスタに保存されます。

表2 DIV命令の構造
ソース・オペランドのサイズ演算余り
8ビットAX*ソースオペランドALAH
16ビットDX:AX*ソースオペランドAXDX
32ビットEDX:EAX*ソースオペランドEAXEDX
sample05
#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にディスティネーション・オペランドのみを指定します。これらの命令では、ディスティネーション・オペランドで指定したレジスタまたはメモリ領域の現在の値からインクリメントまたはデクリメントします。

sample06
#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となります。

次のページ
論理演算

この記事は参考になりましたか?

インラインアセンブラで学ぶアセンブリ言語連載記事一覧

もっと読む

この記事の著者

赤坂 玲音(アカサカ レオン)

平成13年度「全国高校生・専門学校生プログラミングコンテスト 高校生プログラミングの部」にて最優秀賞を受賞。2005 年度~ Microsoft Most Variable Professional Visual Developer - Visual C++。プログラミング入門サイト WisdomSoft の管理人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/442 2006/08/08 20:48

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー