分かりやす〜い
コンピュータ技術情報

TOPに戻る
▼Processor
簡単なアセンブラ言語
┣ 高級、低級の意味と
┃ レジスタ、mov、add

┣ 計算を行う
┣ プログラムを作る
┣ loop命令
┣ Indexレジスタの
┃ 役割とレジスタの
┃ 大きさ
┣ リピートプリフィック
┃ ス・ストリング命令

┣ inc・dec命令
┣ MMX技術
┣ MMXレジスタ
┣ MMX・SIMD・SSE
┣ CMP・JMP命令
┣ 関数とパラメータ
┣ スタック領域
┣ ESPレジスタ
┣ セグメント・
┃   call・ret

┣ コードファイルを作る
┣ コードファイル説明1
┣ コードファイル説明2
┗ マイクロコード

Copyright(C) 2001-2002.ugpop. All rights reserved.




■デジタル用語辞典:
■ 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)」


次回は、今回作成したプログラムをもっと簡単な物に改造した
いと思います。



▲このページの上へ

▲このページの上へ

←前に戻る    ▲このページの上へ    続きを読む→

▲このページの上へ

▲このページの上へ