○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++