コメント
__asm文やブロックでは、C言語やC++言語で使われている/* */形式の複数行コメントや//形式の単一行コメントに加えて、アセンブリ言語のコメント;を利用することができます。このコメントはC++言語の//と同様に単一行コメントです。セミコロン;からコメントが開始し、改行文字でコメントが終了します。その間の文字列は、すべてコンパイル時に無視されます。
#include <stdio.h> int main() { __asm nop ;何もしない __asm { nop ; アセンブリ言語のコメント nop /*C言語のコメント*/ nop //C++言語のコメント } return 0; }
サンプル3は、__asm文やブロックのインラインアセンブラ内で;形式のコメントを使った例です。NOP命令は、何もしない事を表す命令なので、このプログラムの実行結果は何も表示されずに即座にプログラムが終了します(ただし、NOP命令は命令としてCPUに読み込まれるため、機械語として存在します。コメントのようにコンパイル時に削除されるものではありません)。
MOV命令とレジスタ
高水準言語では、CPUとメモリ間のデータ転送を意識する必要はありませんでしたが、アセンブリ言語では CPUとメモリ間のデータ転送すら逐一命令しなければなりません。このとき、メモリからCPUに読み込んだデータを保存する場所がレジスタと呼ばれる小さな記憶領域です。
レジスタはCPUの内部に直接存在しているため、最も高速に動作する記憶領域です。計算などを行うには、最初に必要なデータをレジスタに保存してから計算し、その結果をメモリにストアする作業が必要になります。
メモリはアドレスで管理されますが、レジスタにはすべて名前が割り当てられています。アセンブリ言語を使えば、各レジスタにレジスタ名からアクセスすることができます。レジスタにはさまざまな種類がありますが、データの読み込みに利用するレジスタは汎用レジスタと呼ばれる32ビットのレジスタです。

図1のEAX、EBX、ECX、EDXの4つのレジスタは、レジスタの中で最も基本的なレジスタです。これらのレジスタは、演算、アドレス計算、メモリポインタのオペランドとして使われるものです。ただし、レジスタがどのような形で使われるかは命令によって異なります。機械語の世界では、レジスタを直接操作する命令もあれば、命令を実行した結果としてレジスタに影響するものもあります。
図1のように、レジスタは32ビットですが、その一部分だけを利用することもできます。例えばEAXとした場合は32ビットのレジスタ全体を利用しますが、AXと記述した場合は下部16ビットだけを利用します。さらにAHやALとした場合は下位16ビットをさらに分割し、AHが上位8ビット、ALが下位8ビットとして機能します。
レジスタを利用する命令で、かつアセンブリ言語の中で最も利用頻度の高い、一般的、基本的な命令はMOVです。MOVは、その名前から想像できるようにMove(移動する)を省略した命令です。MOV命令を使えば、メモリからレジスタにデータを読み込んだり、レジスタのデータをメモリにストアすることができます。
mov dest, src
MOV命令は、第2オペランドsrcを第1オペランドdestにコピーします。このとき、第2オペランドのことをソース・オペランド、第1オペランドのことをデスティネーション・オペランドとも呼びます。
通常、EAXレジスタに計算用のデータや結果の格納など、プログラムの計算に利用するデータを保存します。EBXとEDXにはメモリ上のアドレスを表す数値などのポインタを保存し、ECXにはループ処理などに利用されるカウンタ用の値を保存します。
このように、レジスタにはそれぞれ役割があるので、変数のように適当に利用してよいというわけではありません。
#include <stdio.h> int main() { unsigned int i; unsigned short int s; char ch1, ch2; __asm { mov eax, 0x44434241; mov i, eax; mov s, ax; mov ch1, al; mov ch2, ah; } printf("i=%X\n", i); printf("s=%X\n", s); printf("ch1=%c, ch2=%c\n", ch1, ch2); return 0; }
i=44434241 s=4241 ch1=A, ch2=B
サンプル4は、EAXレジスタに対して0x44434241を設定し、その後にunsigned int型の変数i にEAXレジスタの値をストアしています。unsigned intは32ビットのメモリ領域であり、EAXも32ビットのレジスタなので、変数iにはEAXレジスタの値がそのまま格納されます。これに対してunsgined short int型の変数sにAXレジスタの値をストアした場合は、双方共に16ビットとなります。AXレジスタは EAXレジスタの下位16ビットの部分を表しているため、変数sにストアされる値は 0x4241となります。
最後に、char型の変数ch1とch2に、それぞれALレジスタとAHレジスタの値をストアしています。ALレジスタは EAXレジスタの下位 8 ビットであり、AHレジスタは続く8ビットなので、結果としてch1に0x41、ch2に0x42が格納されることとなります。
実行結果を見れば、変数s、ch1、ch2には、それぞれ EAX レジスタの一部の値が格納されていることを確認できます。このように、AL、AH、AX レジスタは、すべて EAX レジスタの一部を共有しています。
ちなみに、サンプル4のアセンブリ文の末尾にコメントの開始を意味するセミコロン;を指定しているのは、文の終端を直観的に確認できるようにして可読性を向上させるためのものです。動作への影響や構文的な意味はありません。
ポインタから間接参照する
通常、EBXレジスタにはメモリアドレスを設定します。EBXレジスタを利用することによって、メモリアドレスからデータを間接的に読み書きすることができます。例えば、C 言語で有効なポインタ型変数が存在している状態で、インラインアセンブラからポインタを使って間接参照を行いたい場合、EBXレジスタにポインタの値(メモリアドレス)を読み込み、EBXレジスタからデータを読み書きしなければなりません。MOV命令で直接ポインタ型変数を指定しても、ポインタが保持しているアドレスが転送されるだけで、間接的に参照することはできません。
アセンブリ言語では、メモリアドレスからデータを読み書きするには、適切なメモリアドレスを保存している EBXレジスタを[ ]で囲みます。アドレス計算を手動で行わなければならない純粋なアセンブリでは、アドレスを表す即値を[ ]で囲んで、定数のアドレスから直接メモリを参照することもできますが、アドレス計算を行わないマクロアセンブラやインラインアセンブラでは不要です。
#include <stdio.h> int main() { int v1 = 10, v2; int* p = &v1; __asm { mov ebx, p ;アドレスを EBX にロード mov eax, [ebx] ;EAX に *p をロード mov v2, eax ;EAX の値を v2 にストア mov [ebx], 100 ;100 を *p にストア } printf("v1=%d, v2=%d\n", v1, v2); return 0; }
v1=100, v2=10
サンプル5は、C言語で初期化された有効なポインタ型変数pをEBXレジスタに読み込みます。ポインタ型変数の値はメモリアドレスなので、ポインタの値をEBXに読み込んで間接参照することができます。EBXに読み込んだアドレスからEAXに値を読み込んでいます。EAXに読み込まれる値はポインタ型変数pが参照している変数v1の内容です。EAXに読み込んだ値はv2変数にストアし、さらにEBXから間接参照して即値100をv1にストアしています。
このように、汎用レジスタEBXを利用することによってメモリアドレスからメモリを参照することができます。
