CPUについて

x86系32bitCPUのについてごく簡単な説明をします.



レジスタ

レジスタとはCPU内部で使える変数のことです.

C言語ではいくらでも変数を使うことが出来ますが, アセンブリ言語 (以下ASMと略) では, 変数に相当するものは10個もありません. それ以上使いたい場合はメモリに保存する必要があります. 逆に言えばどんな言語の変数でも大抵メモリを利用して実現されています. ただレジスタを使うのに比べて遅くなるので, 小回りの利いたルーチンを書く場合には如何に上手くレジスタを使い回すかが鍵となります.

通常のプログラミングの時に必要なレジスタには次のものがあります.

1. eax, ebx, ecx, edx, esi, edi, ebp, espという32bit汎用レジスタ

汎用なので様々な用途に用いられます. 但し, espはスタック領域を指すためだけに使われます. スタック領域とはC言語で言うところの自動変数や関数のパラメータを保存する場所です.

割り込みを禁止すればespを使うこともできますがデバッグがめちゃ困難です.

これらは最初のeを抜くことで16bit汎用レジスタを表し, 更にax, bx, cx, dxの4つは二つの8bitレジスタに分けて使うことができます.

例えばeax=12345678h (数字の末尾にhをつけると16進数を表す) のとき, ax=5678h, ah=56h, al=78hとなります.
hとlは16bitレジスタの上位 (high), 下位 (low) という意味です.
bxや他のレジスタについてもbh, blなどとして使うことが出来ます.

2. cs, ds, ss, es, fs, gsという16bitセレクタ (セグメント) レジスタ

メモリを参照する時に使用します. このうちcs, ds, ssは非常に大切ですが省略可能なため普通は殆んど意識しません.

3. (e)flというフラグレジスタ

演算結果やCPUの状態を表すbitが集まったレジスタです. 主に条件分岐の時使われます.

4. eipというインストラクションポインタ

この数字が入っている場所の命令がその瞬間実行されています. 従って自動的にどんどん代わっていきます. プログラム内でジャンプする時はこの値を書き換えます (が, 通常意識しません).

5. st(0), ..., st(7)という浮動小数点演算に使われるレジスタ

FPUレジスタと呼ばれます. 浮動小数点演算のために使います. sin, cosなどの計算も1命令で出来ます.

6. mm0, ..., mm7という64bitのMMXレジスタ

8bit, 16bit, 32bitの整数をいくつか同時に計算したい時に使われます. 後述のSSEレジスタと合わせてSIMD用レジスタとも呼ばれます. 飽和演算, 積和演算などもサポートしています.
Pentium MMXの頃の実装上の制限からFPUレジスタと同時に使うことは出来ません.

7. xmm0, ..., xmm7 という128bitのSSEレジスタ

PentiumIII, 4やAthlonXPなどでサポートしています(ノーマルAthlonにはありません). MMXレジスタの浮動小数点演算版でfloat x 4を並列実行できます. こちらも参照してください.


メモリとアドレス

アドレスとはどこのメモリを指すかという数字のことです.

全てのメモリには一つずつ通し番号が振り当てられていて, これを物理アドレスと言います.
しかし実際にメモリを参照する時には, 物理アドレスで指定するのではなく OSの管理下にある仮想アドレスで指定します. これには セレクタとオフセットという二つの数字の組を利用します.
セレクタには, 実際に対応する物理アドレスとか そのメモリが書き込み不可であるとかの, 様々な情報が入っている場所の番号が入っています (つまりセレクタ自体の番号にはラベル以外の意味はありません).
オフセットはセレクタで指定される物理アドレスからの 相対的なアドレスです.

しかし現在のWindowsやLinuxで使われているプロテクトモードのフラットモデルでは, プログラムの中でセレクタを具体的に設定することはまずありません(これは普通OSの仕事です).
またcs, ds, ssにはそれぞれプログラム, データ, スタックが入っている場所 を示す番号が入っていると決まっていて, これを使う時は省略できます.
つまりセレクタは普通にプログラムを組む時は殆ど意識しません.

同じCPUでもリアルモードと呼ばれる状態で動作するOS (例えばMS-DOS) では メモリを参照する時のレジスタの扱いに面倒なルールがたくさんあり, 大変でした. またセレクタ(セグメントと呼ぶのが一般的でしたが)も常に意識する必要がありましたが, もはや遠い思い出です.


アドレッシング

アドレッシングというのはアドレスの指定方法です.

次のような形式をとることが出来ます.

sel : [disp32 + base + index * scale]

ここでselは16ビットセグメントレジスタ, disp32は32ビット定数, baseは汎用レジスタ, indexはespを除く汎用レジスタ, scaleは1, 2, 4, 8のいずれかの定数です.
別に全部指定しないといけないわけではなくいずれも省略可能です. それからbaseにebp, espを指定した時はselを省略すると ssを指定したものと見なされます(それ以外はdsが省略されたと見なされる). しかしこの一文は無視してよいです.

例えば

int buf[1024];

と宣言された配列にアクセスする場合

mov eax, [buf + ecx * 4]

とすれば

eax = buf[ecx];

という意味になります (sizeof(int)=4なので).


御意見は光成滋生<herumi@nifty.com>までお願いします