
■ Indexレジスタの役割とレジスタの大きさ ■
前回はアセンブラ言語を使用して簡単な足し算を5回繰り返し
行なうプログラムを作成しました。
以下に前回作成したプログラムを示します。
─↓loop.c───────────────────────
#include <stdio.h>
int main()
{
int a=5;
int b=3;
int result=0;
result = sub(a, b);
printf("result:%d\n", result);
return 0;
}
int sub(int a, int b)
{
_asm{
mov eax, a
mov ebx, b
mov ecx, 5
LOOP1:
add eax, ebx
loop LOOP1
}
}
─↑ここまで──────────────────────
と、ここで前回、各関数の宣言文を付け忘れていたので書き込
んでおきましょう。
#include <stdio.h>
int main(void); ←追加
int sub(int, int); ←追加
これでコンパイラが各関数の型を認識するようになります。
前回正常動作していたのは、たまたまコンパイラが関数のデフ
ォルトをINT型と認識していた為と思われます。
さて、上のリストでは、「loop」命令を使用して繰り返し足し
算を行なわせているのでした。
そして、繰り返し回数は「ecx」レジスタに格納しておく事も
説明しました。
今回は足し算を5回行なうのではなく、データのコピーを行な
う関数を作成してみます。
「データのコピーを行なう」と言っても、難しく考える事はあ
りません。
メモリに置いてある物を別の場所へ「移動」してあげるだけで
す。
インテルプロセッサでデータを「移動」する命令は何だったで
しょうか?
簡単ですね。「MOV」命令です。
今回もこの「MOV」命令を使用します。
前回作成した"loop.c"というファイルをコピー&貼り付けして、
新しく出来たファイルを"loop2.c"という名前に変えておきま
しょう。
今回はこの"loop2.c"に変更を加えていきます。
まず、文字列を扱うようにする為に、メイン関数を以下の様に
書き換えます。
書き換えた物を以下に示します。
─↓loop2.c──────────────────────
int main()
{
int i;
char a[8]={ 0, 1, 2, 3, 4, 5, 6, 7};
char b[8]={10,11,12,13,14,15,16,17};
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
sub(&a[0], &b[0]);
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
return 0;
}
─↑ここまで──────────────────────
C言語が分かる方はすぐに分かると思いますが、簡単な説明だ
け行ないます。
メイン関数の1番初めは「a」というデータと「b」とい
うデータを用意してあげている部分です。
char a[8]={ 0, 1, 2, 3, 4, 5, 6, 7};
char b[8]={10,11,12,13,14,15,16,17};
ここではデータa、データbをそれぞれ8個ずつ用意しました。
次に「printf」文でそれぞれのデータの中身を表示します。
データは8個ずつあるので8回繰り返し表示してあげます。
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
そして「sub」という関数を呼び出しています。
これの中身は今から作成します。
sub(&a[0], &b[0]);
最後に「sub」を呼び出した事によってデータにどんな変化が
あったかを見る為に、もう1度「printf」文でそれぞれのデー
タを表示します。
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
メイン関数の説明は以上です。
では「sub」関数を作成しましょう。
今まで「sub」関数は数値を扱う為に、
int sub(int a, int b)
となっていましたが、今回は8ビットデータのアドレスを扱え
るように以下の様に変更します。
int sub(char* a, char* b)
それと忘れない内に宣言文の方も変更しておきましょう。
int sub(char*, char*);
これで「a」と「b」には8ビットデータのアドレスが入るよう
になります。
「アドレス」というのは、「メモリ上の何番目か」を表す物で
す。
例えば、先ほどの2つのデータ「a」と「b」が、メモリ上に以
下の図の様に格納されていたとします。
メモリ
┌───┐
0│ │
├───┤
1│ │
├───┤
・
・
・
・
├───┤
99│ │
├───┤─┐
100│ 0 │ │
├───┤ │
101│ 1 │ │
├───┤ │a
102│ 2 │ │
├───┤ │
103│ 3 │ │
├───┤ │
・
・
・
・
├───┤─┐
200│ 10│ │
├───┤ │
201│ 11│ │
├───┤ │b
202│ 12│ │
├───┤ │
203│ 13│ │
├───┤ │
・
・
・
・
この例の場合、「a」の先頭のアドレスは、メモリ上の100番目
にあるので「100」となります。
また、「b」の先頭のアドレスはメモリ上の200番目にあるので
「200」となります。
(勿論「100」や「200」といったアドレス情報は1つの
例であり、御使用の環境によって、さまざまな値となります。)
今回は8ビットデータである「b」を「a」にコピーする関数を
作成しましょう。
まずはサブ関数が呼び出された事を分かりやすくする為に、サ
ブ関数の先頭で文字列を表示するようにしましょう。
int sub(char* a, char* b)
{
printf("sub routine called\n");
「printf」文でサブルーチンが呼び出されましたよ、というメ
ッセージを表示するようにしました。
次にインラインアセンブラの解説です。
まず、データをコピーするプログラムを作成したいので、いっ
たん、このアドレス情報をレジスタに移動しておきます。
_asm{
mov edi, a
mov esi, b
新しいレジスタが出てきました。
「edi」レジスタと「esi」レジスタです。
通常、アドレス情報をレジスタに格納する時は、「edi」レジ
スタや「esi」レジスタといった、レジスタ名称の最後が「i」
となっているレジスタに入れる、という習慣があります。
そしてこの「edi」や「esi」の事を「Indexレジスタ」と総称
して呼びます。
何故アドレス情報を扱う時にIndexレジスタを使用するのかと
言うと、昔のインテルプロセッサからの習慣により、Indexレ
ジスタを使用しないと動作しない命令があったり、Indexレジ
スタを使用した方が動作が早かったりする場合があるからです。
他のレジスタ(EAXやEBX等)を使っても使えない事はありませ
んが、それでは使えない命令などが出てきて効率の悪いコード
となってしまうので、殆ど使われる事はありません。
アドレス情報を扱う時は「edi」レジスタや「esi」レジスタ等
の「Indexレジスタ」を使用した方がインテルプロセッサにと
って都合が良いのです。
ちなみに、もう少し詳しく言うと、「esi」レジスタの「si」
とは「Source Index」(元インデックス)の略で、「edi」レ
ジスタの「di」は「Destination Index」(先インデックス)
の略です。
「コピー元」と「コピー先」という事を考えると覚えやすいレ
ジスタ名称となっていますね。
さて、アドレス情報をレジスタに格納したので、後は「loop」
命令と「mov」命令を使用してコピーする処理を作成しましょ
う。
まず、コピー元のメモリからレジスタへデータを移動します。
mov al, [esi]
メモリからデータを読み出す時は[](大括弧)でくくってあげ
ます。
これで、例えば「esi」レジスタに「200」という値が入ってい
たとすると、メモリ上の200番目のデータがレジスタ「al」に
格納されます。
以下にそのイメージを示します。
レジスタ メモリ
al │ │
┌───┐ mov al, [esi] ├───┤
│ 10│←────────200│ 10│
└───┘ ├───┤
201│ 11│
├───┤
202│ 12│
├───┤
203│ 13│
├───┤
ここでも新しいレジスタ「al」が出てきました。
実はこれは「eax」レジスタの8ビットの大きさの呼び方なの
です。
32ビットの大きさの時は「eax」レジスタと呼び、16ビッ
トの大きさの時は「ax」レジスタと呼び、8ビットの大きさの
時は「ah」「al」レジスタという呼び方をします。
以下に「eax」レジスタを32ビット、16ビット、8ビット
それぞれの大きさで扱う時のイメージを示します。
EAX(32BIT)
┌───────────┐
│ AX(16BIT) │
┌─────┐
│ │
┌─────┬──┬──┐
│ │ AH │ AL │
└─────┴──┴──┘
初期のインテルプロセッサの頃は「AH」や「AL」レジスタしか
無かったのですが、互換性を維持したまま機能を拡張する内に、
このように同じレジスタでも大きさによって複数の呼び方が出
てきてしまったのです。
ここでは「eax」レジスタの例を示しましたが、「eax」レジス
タだけでなく、「ebx」や「ecx」レジスタなどでも同じように
「bx」「bh」「bl」、「cx」「ch」「cl」等と大きさによって
呼び方が変わってきます。
EBX(32BIT)
┌───────────┐
│ BX(16BIT) │
┌─────┐
│ │
┌─────┬──┬──┐
│ │ BH │ BL │
└─────┴──┴──┘
ECX(32BIT)
┌───────────┐
│ CX(16BIT) │
┌─────┐
│ │
┌─────┬──┬──┐
│ │ CH │ CL │
└─────┴──┴──┘
ただし、「esi」や「edi」レジスタのようにレジスタ名の最後
が「i」となっているレジスタ(インデックスレジスタと総称
するのでしたね。)は16ビットの大きさの呼び方があるだけ
です。
ESI(32BIT)
┌───────────┐
│ SI(16BIT) │
┌─────┐
│ │
┌─────┬─────┐
│ │ │
└─────┴─────┘
EDI(32BIT)
┌───────────┐
│ DI(16BIT) │
┌─────┐
│ │
┌─────┬─────┐
│ │ │
└─────┴─────┘
何故かと言うと、インデックスレジスタは、最初からアドレス
情報を扱うように設計されたレジスタだからです。
ここでの例では、8ビットの大きさのデータをレジスタに格納
したいので、
mov al, [esi]
というように8ビットの大きさのレジスタを使用しました。
次に、このデータをコピー先のメモリに格納したいので以下の
様にします。
mov [edi], al
これで「データb」(コピー元)のデータを「データa」(コピ
ー先)に格納できました。
ただし、これだけでは1バイト分しかコピーしていないので、
データaの大きさ分、ループ命令を使って繰り返してあげます。
その際、1バイトコピーする毎にコピー元、コピー先アドレス
をそれぞれ1つづカウントアップしていきます。
以下にデータbをデータaにコピーするアセンブラコードを示し
ます。
─↓ここから──────────────────────
mov ecx, 8 ;8回繰り返す
LOOP1:
mov al, [esi] ;←データbをレジスタに移動
mov [edi], al ;←レジスタの値をデータaに移動
add edi, 1 ;←データaのアドレスを1加算
add esi, 1 ;←データbのアドレスを1加算
loop LOOP1 ;←ecxの回数分ループ
}
─↑ここまで──────────────────────
サブ関数はこれで完成です。
以下にサブ関数の完成形を示します。
─↓ここから──────────────────────
int sub(char* a, char* b)
{
printf("sub routine called\n");
_asm{
mov edi, a ;データaのアドレスを格納
mov esi, b ;データbのアドレスを格納
mov ecx, 8 ;8回繰り返す
LOOP1:
mov al, [esi] ;←データbをレジスタに移動
mov [edi], al ;←レジスタの中身をデータaに移動
add edi, 1 ;←データaのアドレスを1加算
add esi, 1 ;←データbのアドレスを1加算
loop LOOP1 ;←ecxの回数分ループ
}
}
─↑ここまで──────────────────────
● ●
● ビルドする ●
● ●
コマンドプロンプト上で以下の様に入力してください。
sc loop2.c -j
これでビルドされ、"loop2.exe"という実行ファイルが作成され
るはずです。
● ●
● 実行する ●
● ●
コマンドプロンプト上で以下の様に入力してください。
loop2.exe
これでサブ関数を呼び出す前のデータaとデータbの中身が表示
され、その後、サブ関数を呼び出した後のデータaとデータbの
中身が表示されます。
実行結果:
a[0]:0 b[0]:10
a[1]:1 b[1]:11
a[2]:2 b[2]:12
a[3]:3 b[3]:13
a[4]:4 b[4]:14
a[5]:5 b[5]:15
a[6]:6 b[6]:16
a[7]:7 b[7]:17
sub routine called
a[0]:10 b[0]:10
a[1]:11 b[1]:11
a[2]:12 b[2]:12
a[3]:13 b[3]:13
a[4]:14 b[4]:14
a[5]:15 b[5]:15
a[6]:16 b[6]:16
a[7]:17 b[7]:17
サブ関数を呼び出した後はデータbがデータaにコピーされたの
が分かりますね。
今回はデータをコピーするプログラムを作成してみました。
いつもより少しだけ覚える事が多かったようなのでちょっとま
とめてみます。
1.「edi」「esi」レジスタは主にアドレス情報を扱い、イン
デックスレジスタと総称される。
2.各レジスタは扱える大きさによって呼び方が変わる。
例)「eax(32BIT)」→「ax(16BIT)」→「ah(8BIT)」
「al(8BIT)」
「esi(32BIT)」→「si(16BIT)」
次回は、今回作成したプログラムをもっと簡単な物に改造した
いと思います。