○MASMインラインアセンブラ


コメント文
値の表現
レジスタは基本的に以下のものが使用できます。
データ処理32bitレジスタは、8bitのレジスタ4個で構成されています。(下位互換性のため)
nop命令
mov命令(値をコピーさせる命令)
xchg命令(レジスタの値を交換)
movzx命令(サイズが違うレジスタにコピー)
add命令(足し算)
sub命令(引き算)
mul命令(掛け算)
div命令(割り算)
jmp命令
call ret命令(BASICで言うGOSUB RETURNのような感じ)
push pop命令(スタックに保存と取り出し)
条件分岐
インクリメントとデクリメント
ループ
実際にインラインアセンブラを使用する時には
FPU
MMX



















■コメント文

アセンブラ内では";"の後ろにコメントを書きます。
もちろんインラインアセンブラですのでC/C++のコメントも使用できます。

_asm{
    ;-----------コメント文のサンプル-------------
    mov eax,a        ;変数aの値をレジスタeaxにコピー
    mov b,eax        ;レジスタeaxの値を変数bにコピー
    ;-----------------------------------------
    //もちろんC++のコメント文も使用できます。
    /*Cのコメント文も使用できます。*/
}

■値の表現

文字    'A'

10進数    16
16進数    010H    ←先頭に0が必要、先頭に0が無いとAなどが文字とみなされる
8進数    20Q
2進数    10000B

■レジスタは基本的に以下のものが使用できます。

▽データ処理32bitレジスタ

eax    Accumulator     ←通常の値の格納に使用可能
ebx    Base register    ←通常の値の格納に使用可能
ecx    Count register    ←通常の値の格納に使用可能
edx    Data register    ←通常の値の格納に使用可能

▽アドレス指定32bitレジスタ

esp    Stack Pointer
ebp    Base Pointer
esi    Source Index    ←通常のポインタの格納に使用可能
edi    Destination Index    ←通常のポインタの格納に使用可能

■データ処理32bitレジスタは、8bitのレジスタ4個で構成されています。(下位互換性のため)

32bit    |        eax        |
16bit    |    ax   |    bx   |
8bit    | ah | al | bh | bl |

▽つまりサイズによって次のように書きます
□8bitの場合

    char a=10;
    char b=0;
    _asm{
        mov ah,a
        mov b,ah
    }

□16bitの場合

    short a=10;
    short b=0;
    _asm{
        mov ax,a
        mov b,ax
    }

□32bitの場合

    long a=10;
    long b=0;
    _asm{
        mov eax,a
        mov b,eax
    }

■nop命令

何もしない命令です。

■mov命令(値をコピーさせる命令)
long aの値をeaxレジスタを介してlong bにコピーします。

#include <stdio.h>

int main(char* argv[])
{
    long a=10;
    long b=0;

    _asm{
        mov eax,a
        mov b,eax
    }
    
    printf("%d\n",b);
    return 0;
}

▽ポインタのコピー

#include <stdio.h>

int main(char*args[]){
    char*a="test";
    char*b;

    _asm{
        mov esi,a
        mov b,esi
    }

    printf("%s\n",b);
    return 0;
}

処理結果
test

▽ポインタを進めることもできます。

#include <stdio.h>

int main(char*args[]){
    char*a="test";
    char*b;

    _asm{
        mov edi,a
        inc edi//インクリメント(1を足す)
        mov b,edi
    }

    printf("%s\n",b);
    return 0;
}

処理結果
est

■xchg命令(レジスタの値を交換)

#include <stdio.h>

int main(char*args[]){
    long a=10;
    long b=20;
    _asm{
        mov eax,a
        mov ebx,b
        xchg eax,ebx//レジスタの値を交換します。
        mov a,eax
        mov b,ebx
    }
    
    printf("%d %d\n",a,b);
    return 0;
}

■movzx命令(サイズが違うレジスタにコピー)

#include <stdio.h>

int main(char*args[]){
    long a=10;
    short b;
    _asm{
        mov eax,a
        mov bx,b
        movzx bx,eax//32bitレジスタを16bitレジスタにコピー
        mov b,bx
    }
    printf("%d\n",b);
    return 0;
}
-----------------------------------------------------
#include <stdio.h>

int main(char*args[]){
    long a;
    short b=10;
    _asm{
        mov eax,a
        mov bx,b
        movzx eax,bx//16bitレジスタを32bitレジスタにコピー
        mov a,eax
    }
    printf("%d\n",a);
    return 0;
}

■add命令(足し算)
aとbを足して答えをcに入れます

#include <stdio.h>

int main(char* argv[])
{
    long a=10;
    long b=10;
    long c;

    _asm{
        mov eax,a
        mov ebx,b
        add eax,ebx
        mov c,eax
    }
    
    printf("%d\n",c);
    return 0;
}

■sub命令(引き算)
aからbを引いて答えをcに入れます

#include <stdio.h>

int main(char* argv[])
{
    long a=20;
    long b=10;
    long c;

    _asm{
        mov eax,a
        mov ebx,b
        sub eax,ebx
        mov c,eax
    }
    
    printf("%d\n",c);
    return 0;
}

■mul命令(掛け算)
aとbを掛け算して答えをcに入れます

#include <stdio.h>

int main(char* argv[])
{
    long a=10;
    long b=20;
    long c;

    _asm{
        mov eax,a
        mov ebx,b
        mul ebx    //eaxは暗黙的に必ず使用される。
        mov c,eax
    }
    
    printf("%d\n",c);
    return 0;
}

■div命令(割り算)
aとbを割り算して答えをcに入れます

#include <stdio.h>

int main(char* argv[])
{
    long a=4;
    long b=2;
    long c;
    long d;

    _asm{
        mov edx,0//計算に使用されるため必ず初期化
        mov eax,a
        mov ebx,b
        div ebx    //eax edx は暗黙的に必ず使用される。
        mov c,eax//計算結果
        mov d,edx//余り
    }
    
    printf("%d\n",c);
    return 0;
}

■jmp命令
無条件でジャンプします

#include <stdio.h>

int main(char* argv[])
{
    long a=0;

    _asm{
        mov eax,10
        jmp LABEL2//LABEL2に無条件ジャンプ
        mov eax,0//ここの処理は飛ばされる
        LABEL2://ラベル
        mov a,eax
    }
    
    printf("%d\n",a);
    return 0;
}

処理結果
10

■call ret命令(BASICで言うGOSUB RETURNのような感じ)

#include <stdio.h>

int main(char* argv[])
{
    long a=0;

    _asm{
        mov eax,10
        call LABEL1//LAVEL1をコールする
        jmp LABEL2//LABEL2に無条件ジャンプ
        LABEL1:
        mov eax,0
        ret//呼び出された場所に戻る
        LABEL2://ラベル
        mov a,eax
    }
    
    printf("%d\n",a);
    return 0;
}

処理結果
0

■push pop命令(スタックに保存と取り出し)

#include <stdio.h>

int main(char* argv[])
{
    long a,b,c;
    _asm{
        push 10//内容をスタックに保存する
        push 20//内容をスタックに保存する
        push 30//内容をスタックに保存する

        //pushで保存した内容は必ずpopで取り出す必要があります。

        pop a//内容をスタックから取り出す
        pop b//内容をスタックから取り出す
        pop c//内容をスタックから取り出す
    }
    
    printf("%d %d %d\n",a,b,c);
    return 0;
}

処理結果
30 20 10

■条件分岐

レジスタ比較命令(cmp)を実行した結果によりラベルにジャンプします。

je    ==
jne    <>

▽符号なし
ja    >
jae    >=
jb    <
jbe    <=

▽符号あり
jg    >
jge    >=
jl    <
jle    <=

#include <stdio.h>

int main(char*args[]){

    long a=0;
    _asm{
        mov eax,0
        mov ebx,1
        cmp eax,ebx//レジスタ比較命令
        jne LABEL1//cmpの結果が<>の時LABEL1にジャンプします。
        jmp END//無条件でENDにジャンプします。
        LABEL1:
        mov a,10
        END:
    }
    printf("%d\n",a);
    return 0;
}

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

#include <stdio.h>

int main(char*args[]){
long a;
long b;
    _asm{
        mov eax,10
        mov ebx,10
        inc eax//インクリメント
        dec ebx//デクリメント
        mov a,eax
        mov b,ebx
    }

    printf("%d %d\n",a,b);
    return 0;
}

処理結果
11 9

■ループ

#include <stdio.h>

int main(char*args[]){

    _asm{
        mov ecx,10//ecxが暗黙的に使用されます
        LOOPSTART:

        //この中が10回ループします。

        loop LOOPSTART//ecxの値から1を引き、値が0になるまでジャンプします。
    }
    return 0;
}

■実際にインラインアセンブラを使用する時には
すでに前後でレジスタを使用中かもしれないので、
使用するレジスタを退避させたほうが安全だと思われます。

#include <stdio.h>

int main(char*args[]){

    //Cによる各種処理

    _asm{
        push eax//スタックに退避
        push ebx
        push ecx
        push edx
        push esi
        push edi

        //各種処理

        pop edi//スタックから復帰
        pop esi
        pop edx
        pop ecx
        pop ebx
        pop eax
    }
    
    //Cによる各種処理    

    return 0;
}

▼レジスタ退避をすると、ループの真ん中にCの構文を挟むこともできます。

#include <stdio.h>

int main(char*args[]){

    long a;

    _asm{
        push ecx//スタックにecxを退避
        mov ecx,10//暗黙的に使用されます
        LOOPSTART:
        mov a,ecx
        push ecx//スタックにecxを退避
    }

    printf("%d\n",a);

    _asm{
        pop ecx//スタックからecxを復帰
        loop LOOPSTART//ecxの値から1を引き、値が0になるまでジャンプします。
        pop ecx//スタックからecxを復帰
    }
    return 0;
}

処理結果

10
9
8
7
6
5
4
3
2
1


■FPU---------------------------------------------------

FPUレジスタは8段スタック型になっています。
8段以上入れようとすると先に入れたものから消えますし、余分に取り出そうとするとゴミが出てきます

-----------
|    st   |
-----------
|  st(1)  |
-----------
|  st(2)  |
-----------
|  st(3)  |
-----------
|  st(4)  |
-----------
|  st(5)  |
-----------
|  st(6)  |
-----------
|  st(7)  |
-----------


▼PUSH POP
floatだと fld fstp だがdoubleだと fldl fstpl になる

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=10.0001;
    _asm{
        fld i //プッシュします
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
10.0001

▼足し算

#include <stdio.h>

int main(char*args[]){

    float i,j,k;
    i=10.0001;
    j=1;
    _asm{
        fld i //プッシュします
        fadd j//スタックの先頭に足します
        fstp k //ポップします
    }

    printf("%g\n",k);
    return 0;
}

処理結果
11.0001

▼引き算

#include <stdio.h>

int main(char*args[]){

    float i,j,k;
    i=10.0001;
    j=1;
    _asm{
        fld i //プッシュします
        fsubr j//スタックの先頭と引き算をします
        fstp k //ポップします
    }

    printf("%g\n",k);
    return 0;
}

処理結果
-9.0001

▼掛け算

#include <stdio.h>

int main(char*args[]){

    float i,j,k;
    i=10.1;
    j=2;
    _asm{
        fld i //プッシュします
        fmul j//スタックの先頭と掛け算をします
        fstp k //ポップします
    }

    printf("%g\n",k);
    return 0;
}

処理結果
20.2

▼割り算

#include <stdio.h>

int main(char*args[]){

    float i,j,k;
    i=10.1;
    j=2;
    _asm{
        fld i //プッシュします
        fdiv j//スタックの先頭と割り算をします
        fstp k //ポップします
    }

    printf("%g\n",k);
    return 0;
}

処理結果
5.05

▼絶対値

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=-10.01;

    _asm{
        fld i //プッシュします
        fabs //スタックの先頭を絶対値に変換
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
10.01

▼符号の反転

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=10.01;

    _asm{
        fld i //プッシュします
        fchs //スタックの先頭の符号の反転
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
-10.01

▼sin

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=2;

    _asm{
        fld i //プッシュします
        fsin //スタックの先頭のsinを求めます
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
0.841471

▼cos

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=2;

    _asm{
        fld i //プッシュします
        fcos //スタックの先頭のcosを求めます
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
-0.416147

▼√

#include <stdio.h>

int main(char*args[]){

    float i,j;
    i=2;

    _asm{
        fld i //プッシュします
        fsqrt //スタックの先頭の√を求めます
        fstp j //ポップします
    }

    printf("%g\n",j);
    return 0;
}

処理結果
1.41421


■MMX---------------------------------------------------

MMXレジスタ

(注)MMX用の回路は、FPU用の回路と共通になっており、同時に使うことは出来ません。
MMXとFPUモードを切り替える時には、数クロックが無駄になります

MMXレジスタは 64bit幅あります 64bit幅を一つの命令で演算ができます
---------------------------------------
|                    64bit                     |
---------------------------------------

16bit処理命令を使うと 一度に4つの計算が出来ます
---------------------------------------
|   16bit  |           |           |           |
---------------------------------------

8bit処理命令を使うと 一度に8つの計算ができます
---------------------------------------
|8bit|     |     |     |     |     |     |     |
---------------------------------------


▼8bitの符号なし足し算

#include <stdio.h>

void main(){
    unsigned char a[8]={0},b[8]={0},c[8];

    a[0]=3;
    a[1]=2;
    a[2]=3;
    a[3]=4;
    a[4]=5;
    a[5]=250;
    a[6]=251;
    a[7]=252;

    b[0]=4;
    b[1]=4;
    b[2]=4;
    b[3]=4;
    b[4]=4;
    b[5]=4;
    b[6]=4;
    b[7]=4;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        paddb    mm0,mm1    //8bit単位 足し算
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<8;i++)
    printf("%d+%d=%d\n",a[i],b[i],c[i]);
}

処理結果
3+4=7
2+4=6
3+4=7
4+4=8
5+4=9
250+4=254
251+4=255
252+4=0 ←桁あふれをしている


▽上の足し算命令を桁あふれ時には最大値を代入してくれる命令に変更

paddusb    mm0,mm1//byte足し算(飽和演算命令)

処理結果
3+4=7
2+4=6
3+4=7
4+4=8
5+4=9
250+4=254
251+4=255
252+4=255 ←飽和している


▼8bitの符号付き足し算

#include <stdio.h>

void main(){
    char a[8]={0},b[8]={0},c[8];

    a[0]=3;
    a[1]=2;
    a[2]=3;
    a[3]=4;
    a[4]=5;
    a[5]=6;
    a[6]=7;
    a[7]=8;

    b[0]=-4;
    b[1]=-4;
    b[2]=-4;
    b[3]=-4;
    b[4]=-4;
    b[5]=-4;
    b[6]=-4;
    b[7]=-4;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        paddsb    mm0,mm1    //符号付8bit単位 足し算(飽和演算命令)
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<8;i++)
    printf("%d+%d=%d\n",a[i],b[i],c[i]);
}

処理結果
3+-4=-1
2+-4=-2
3+-4=-1
4+-4=0
5+-4=1
6+-4=2
7+-4=3
8+-4=4

▼8bitの引き算

#include <stdio.h>

void main(){
    char a[8]={0},b[8]={0},c[8];

    a[0]=3;
    a[1]=2;
    a[2]=3;
    a[3]=4;
    a[4]=5;
    a[5]=6;
    a[6]=-7;
    a[7]=-8;

    b[0]=4;
    b[1]=4;
    b[2]=4;
    b[3]=4;
    b[4]=4;
    b[5]=127;
    b[6]=127;
    b[7]=127;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        psubb    mm0,mm1    //8bit単位 引き算
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<8;i++)
    printf("%d-%d=%d\n",a[i],b[i],c[i]);
}

処理結果
3-4=-1
2-4=-2
3-4=-1
4-4=0
5-4=1
6-127=-121
-7-127=122 ←飽和している
-8-127=121 ←


▽上の引き算命令を飽和演算命令に変更

psubsb    mm0,mm1//byte引き算(飽和演算命令)

処理結果
3-4=-1
2-4=-2
3-4=-1
4-4=0
5-4=1
6-127=-121
-7-127=-128 ←飽和している
-8-127=-128 ←


▼8bit符号なし引き算

#include <stdio.h>

void main(){
    unsigned char a[8]={0},b[8]={0},c[8];

    a[0]=255;
    a[1]=255;
    a[2]=255;
    a[3]=255;
    a[4]=1;
    a[5]=2;
    a[6]=3;
    a[7]=4;

    b[0]=1;
    b[1]=2;
    b[2]=3;
    b[3]=4;
    b[4]=5;
    b[5]=6;
    b[6]=7;
    b[7]=8;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        psubusb    mm0,mm1    //8bit単位 符号なし引き算(飽和演算命令)
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<8;i++)
    printf("%d-%d=%d\n",a[i],b[i],c[i]);
}

処理結果
255-1=254
255-2=253
255-3=252
255-4=251
1-5=0        ←飽和している
2-6=0        ←
3-7=0        ←
4-8=0        ←


▼16bitの足し算 引き算

#include <stdio.h>

void main(){
    unsigned short a[4]={0},b[4]={0},c[4];

    a[0]=65533;
    a[1]=65533;
    a[2]=65533;
    a[3]=65533;

    b[0]=1;
    b[1]=2;
    b[2]=3;
    b[3]=4;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        paddw    mm0,mm1        //16bit単位 足し算
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<4;i++)
    printf("%d+%d=%d\n",a[i],b[i],c[i]);
}

処理結果
65533+1=65534
65533+2=65535
65533+3=0    ←飽和している
65533+4=1    ←

▽上の足し算命令を桁あふれ時には最大値を代入してくれる命令に変更

paddusw    mm0,mm1        //16bit単位 符号なし足し算(飽和演算命令)

処理結果
65533+1=65534
65533+2=65535
65533+3=65535
65533+4=65535

▼16bit 符号あり足し算

paddsw    mm0,mm1        //16bit単位 符号あり足し算(飽和演算命令)

▼16bit 引き算命令

psubw    mm0,mm1        //16bit単位 引き算
psubsw    mm0,mm1        //16bit単位 符号あり引き算(飽和演算命令)
psubusw    mm0,mm1        //16bit単位 符号なし引き算(飽和演算命令)

▼16bit 掛け算命令

pmullw    mm0,mm1        //16bit単位 かけ算


▼シフト命令

▽左シフト

#include <stdio.h>

void main(){
    unsigned short a[4]={0},b[4];

    a[0]=1;
    a[1]=2;
    a[2]=4;
    a[3]=8;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        psllw    mm0,1    //16bit単位 左へ1シフト
        movq    b,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<4;i++)
    printf("%d : %d\n",a[i],b[i]);
}

処理結果
1 : 2
2 : 4
4 : 8
8 : 16

▽右シフト

psrlw    mm0,1    //16bit単位 右へ1シフト

処理結果
1 : 0
2 : 1
4 : 2
8 : 4

▽算術右シフト(符号付右シフト)

#include <stdio.h>

void main(){
    short a[4]={0},b[4];

    a[0]=-1;
    a[1]=-2;
    a[2]=-4;
    a[3]=-8;

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        psraw    mm0,1        //符号付き右シフト命令
        movq    b,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }

    for(int i=0;i<4;i++)
    printf("%d : %d\n",a[i],b[i]);
}

処理結果
-1 : -1
-2 : -1
-4 : -2
-8 : -4

▼論理演算

▽AND

#include <stdio.h>

void main(){
    unsigned short a[4]={0},b[4]={0},c[4];

    a[0]=120;// 2進数では  1111000

    b[0]=15;// 2進数では  0001111

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        pand    mm0,mm1        //AND命令
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }
    printf("%d\n",c[0]);
}

処理結果
8  ←2進数では  0001000

▽OR

#include <stdio.h>

void main(){
    unsigned short a[4]={0},b[4]={0},c[4];

    a[0]=120;// 2進数では  1111000

    b[0]=15;// 2進数では  0001111

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        por    mm0,mm1        //or命令
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }
    printf("%d\n",c[0]);
}

処理結果
127  ←2進数では  1111111

▽XOR

#include <stdio.h>

void main(){
    unsigned short a[4]={0},b[4]={0},c[4];

    a[0]=120;// 2進数では  1111000

    b[0]=15;// 2進数では  0001111

    _asm{
        movq    mm0,a        //64bitデータをMMXレジスタにコピー
        movq    mm1,b        //64bitデータをMMXレジスタにコピー
        pxor    mm0,mm1        //xor命令
        movq    c,mm0        //64bitデータをMMXレジスタからコピー
        emms            //MMX命令の終了(必ず必要)
    }
    printf("%d\n",c[0]);
}

処理結果
119  ←2進数では  1110111


▲トップページ > Windows と C++