はじめに
アセンブリ言語は、コンピュータのCPUが直接理解することができる命令である機械語を記号化した言語であり、機械語と対の関係にあります。アセンブリ言語の1文は1つの機械語に置き換わります。機械語は2進数だけで構成されるバイナリデータで、命令の意味を表すオペコードと呼ばれる値でCPUに指示を出しています。人間が直接機械語を読み書きする場合は、16進数を使うのが一般的です。どちらにしても、数値だけで表現されるプログラミングは人間が読み書きするには不向きなので、数値の代わりにアルファベットによる記号を与えたものがアセンブリ言語なのです。マシン後に対応したアセンブリ言語の記号のことをニーモニックと呼びます。
しかし、ニーモニックを学習するだけではアセンブリ言語は使えません。問題は、CPUがメモリと通信を行う際に発生するさまざまなアドレスの計算です。機械語の世界では変数や関数という概念は存在せず、式や識別子という概念すら存在しません。単純な加算演算を行うにも、メモリからCPUに値を読み込ませ、加算命令を実行し、実行結果を適切なメモリ領域に戻すという作業を実行する必要があります。
このとき、機械語の世界では、メモリから読み込むデータをメモリアドレスで指定しなければなりません。本来のアセンブリ言語でも変数という概念は存在しないのでアドレスの計算が必要になりますが、アセンブリ言語をさらに拡張したマクロアセンブラを使えば、アドレスの計算を識別子を使って自動化することができます。
VC++のインラインアセンブラであれば、C言語内にアセンブリ言語を組み込む形になるため、C言語の識別子をそのまま利用することができます。複雑なアドレスの計算が不要になるため、C言語が使えるのであれば、インラインアセンブラを利用して比較的容易にアセンブリの世界へ入門することができます。本稿では、32ビットのIntel x86互換プロセッサを想定してアセンブリの解説を行います。
Microsoft Visual C++
VC++のC言語プロジェクトでは、C言語のコードの途中にアセンブリ言語を組み込むことができます。これをインラインアセンブラと呼びます。C言語とアセンブラであれば古いVC++でも利用することができるので、本稿はVC++ 6.0などのユーザーでもご利用いただけます。VC++を持っていない場合は、本稿をきっかけにMicrosoft Visual Studio 2005 Express Editionをお試しください。Visual Studio 2005 Express Editionは、MicrosoftのWebサイトから無償でダウンロードして利用することができます。
Visual C++ 2005 Express Editionは、プロフェッショナル向けの機能の一部が制限されていますが、通常の C/C++言語のプロジェクトであれば十分に利用することができる統合開発環境です。本稿のサンプルはVisual Studio 2005 Professional Editionを使って開発していますが、Express Editionでもインラインアセンブラを試すことができます。
アセンブラを試すには、VC++で空のプロジェクトを作成してC言語のソースファイルを追加してください。Express Editionの場合、[ファイル]-[新規作成]-[プロジェクト]メニューを選択して[新しいプロジェクト]のダイアログを開きます。
左側の[プロジェクトの種類]ツリーから[Visual C++]を選択し、右側の[テンプレート]から[emptyproj]を選択してください。その後、任意のプロジェクト名、およびソリューション名を指定して[OK]ボタンを押します。
作成したプロジェクトにはソースファイルが何も無い状態なので、[ソース ファイル]フォルダを右クリックして[追加]-[新しい項目]を選択します。[新しい項目の追加]ダイアログボックスが表示されるので、左側の[カテゴリ]ツリーから[コード]を選択して、右側の[テンプレート]から[C++ファイル (cpp)]を選択してください。
後は、任意のファイル名を指定して[追加]ボタンを押せばソースファイルが追加されます。本稿ではC言語しか使わないので、.cファイルでかまいません。作成したソースファイルを選択して、任意のコードを記述することができます。
インラインアセンブラの書き方
C言語のコード中にアセンブリ言語を記述するには__asm
キーワードを使います。__asm
キーワードは、VC++専用のMicrosoft独自のキーワードで、C 言語標準のものではありません。アセンブラはC言語コード内では文として扱われるので、__asm
は文を記述できる場所であればどこにでも記載できます。__asm
は、次のように使います。
__asm アセンブリ文 __asm { アセンブリ文のリスト }
__asm
キーワードの直後にアセンブリ言語の単一の文が続きます。記述するアセンブリ言語の文が複数ある場合は { } のブロック内に任意の数のアセンブリ文を記述します。ただし、ここで記述したアセンブリ言語は前後するC言語のコンパイル結果となる機械語に影響を与えるので注意が必要です。本来ならば、十分なアセンブリ言語の知識を持っている開発者が必要に応じて利用するのが__asm
キーワードです。
早速、アセンブリ言語で「Hello world」を出力するコードを書きたいところですが、文字列を出力する概念を機械語レベルで理解するには相応の基礎知識が必要となり、C言語のprintf()
関数を呼び出すだけのプログラムのように単純ではありません。この場では、まず、C言語で宣言した変数にアセンブリ言語から値を代入するサンプルを作成します。
#include <stdio.h> int main() { int i; __asm mov i, 100 printf("i=%d\n", i); return 0; }
i=100
__asm mov i, 100
という文が、C言語内に組み込んだアセンブリ言語です。アセンブリ言語はC言語のように文を入れ子にすることはできないので、末尾をセミコロン;
で終わらせる必要はありません。アセンブリ言語は、ニーモニックから自動的にオペランドを決定することができます。
上記のコードでは、C言語で宣言した変数i
に対して100を代入するプログラムです。アセンブリ言語の用語を使うのであれば、「変数i
のメモリアドレスに即値100をストアする」と表現します。単純に転送と呼んでも通用すると思われますが、一般的には、メモリからCPUに値を読み込むことを「ロード」と呼び、CPUからメモリにデータを書き込むことを「ストア」と呼びます。また、定数のことは「即値」と呼びます。ただし、インラインアセンブラであれば、C言語に内包されているものなので定数と呼んでも違和感はありません。
このように、インラインアセンブラではアセンブリ言語内でC言語の識別子を利用することができます。変数i
は実質的にはデータをストアするべきメモリアドレスに変換されますが、開発者がそれを意識する必要はありません。
複数のアセンブリ文を記述する場合は{ }
で __asm
キーワードに続いてブロックを指定します。これによって、長文のアセンブリ言語を記述することができます。アセンブリ言語の文は改行で区切られるので、複数の文を記述する場合は文の最後で必ず改行しなければなりません。
#include <stdio.h> int main() { char ch1, ch2; __asm { mov ch1, 0x41 mov ch2, 0x42 } printf("ch1=%c, ch2=%c\n", ch1, ch2); return 0; }
ch1=A, ch2=B
サンプル2は、文字型の変数ch1
とch2
にアセンブリ言語で値を代入しています。ch1
には0x41、ch2
には0x42を代入していますが、これらはASCIIコードの「A」と「B」を表しているため、結果はAとBが表示されます。このプログラムのアセンブリ言語は__asm
ブロック内に記述しています。多くの場合、アセンブリ言語によるプログラムは長くなるので、ブロックが利用されることになるでしょう。
サンプル2のコードでは、アセンブリ言語からch1
やch2
に代入する値を0x41や0x42というようにC言語の定数で指定しています。このように、インラインアセンブラで利用する定数はC言語の定数を使うことができます。例えば、上記のコードは数値ではなく'A'
や'B'
という形で文字定数を指定することも可能です。
mov ch1, 'A'
これは有効なコードです。ch1
変数に'A'
定数に相当する値0x41が格納されることに変わりはありません。