今回の主役はポインタレジスタです。
1 今回のプログラムの概要
また1から100までの合計を求めるのですが、今回は趣向を変えて、要素数が100のunsigned shortの配列を用意し、そこに1から100までの数値を格納してから、すべての要素の合計を求めることにします。
趣向を変えるといっても、これでは骨折り損のくたびれ儲けみたいな回り道で、答えは13baに決まっているのですが、まあお付き合い下さい。
main関数は次のようになりますね。
/*** add by assembly language (with_asm.c) ***/
#include <stdio.h>
extern void loaddata();
extern unsigned short gettotal();
/*
* unsigned short shortary[100];
*/
int main(int argc, char **argv)
{ unsigned short answer;
loaddata(); /* load 1, 2, 3, .. in array */
answer = gettotal(); /* get total of elements */
printf("%x\n", answer);
return 0;
}
配列shortaryはアセンブリ言語側だけで使うのですから、main側では書く必要はありません。
2 セグメントレジスタ
セグメントレジスタとして最もよく使われるのがCS(コードセグメント)とDS(データセグメント)です。ほかにES(エクストラセグメント)とかSS(スタックセグメント)などがあります。なおSSは次回の準主役として登場する予定です。
実行可能ファイルは大きく2つの部分に分かれています。まずアセンブラが生成した機械語の命令が並び、これにデータの部分が続きます。プログラムの実行中、CSは機械語群(コードの部分)の、DSはデータの部分の先頭(いわば起算点)がはいっています。(注1)
3 データの部分の書き方
前回のプログラムはすべて命令で、データはありませんでした。@functionですからコードの部分に決まっていますよね。今回はshortaryというデータの部分がありますから、次のように書く必要があります。アセンブリ言語には配列などという高級な概念はなく、単に200バイトのメモリを確保してくれるだけです。(注2)
/* -- local memory for 'shortary' -- */
.lcomm shortary, (0x02 * 0x0064)
4 ポインタレジスタ
siとdiがポインタレジスタで、いずれも2バイトのサイズです。
この2つのポインタレジスタの値はunsigned short、つまり非負で、起算点を含めて0x10000バイトのメモリを参照することができます。
起算点はデフォールトでDSです。CSを起算点にすることもできますが、機械語が並んでいるだけですから、そこを参照する必要はほとんどないでしょう。(注3)
5 C言語のポインタが苦手の方へ
アセンブリ言語でもC言語でも、アドレスを入れた変数があって、そのアドレスのメモリの内容を読み書きすることができます。
アセンブリ言語でもC言語でも、ポインタは特定のメモリを指していて、そのメモリの内容を読み書きすることができます。
要するに、アドレスとポインタは同じものです。(少し過激でしたか)
もうひとつだけ。配列名をハダカで書いたとき、それは定義により配列の先頭要素のアドレスと同じです。つまり次の式は恒等式です。
shortary == &(shortary[0])
6 セグメントの壁
0x10000(例のムコのゴンザブロウ)バイトの連続したメモリが1セグメントです。
x86系のご先祖様である8086(8088も同じ)はレジスタが2バイトでした。当時はプログラムのデータとしてシステムが提供してくれるメモリは1セグメント以下でした。なぜならポインタレジスタも2バイトだったので、起算点をDSとしてゼロから0xFFFFまで、0x10000個のメモリしか参照できないからです。
もちろんシステムに余裕があればシステムは更に1セグメントのメモリを提供してくれますが、それは最初の1セグメントと連続したものではなく、起算点をエクストラセグメントESなどで指定しなければなりませんでした。大きなデータを扱うとき、これではまことに不便で、技術屋さんはこれを「セグメントの壁」と呼んでいました。
80386からでしたか、レジスタのサイズが4バイトになり、同時に仮想メモリの技術(注4)も開発されて、ついにセグメントの壁は取り払われました。
こういう背景があるので、今や2バイトのsiとdiの出番はなくなり、専らハーフサイズのesiとediが使われています。(注5)次のプログラムで見てみましょう。
7 アセンブリ言語のプログラム
ご覧のとおり、ポインタレジスタはハーフサイズのesiとediが登場しました。前半と後半で主役が交代していますね。別に交代させる必要もないのですが、慣習上si(source index)はメモリからデータを読み込むときに、di(destination index)はデータをメモリに書き込むときに多用されます。
大事なことを書き落としましたが、ポインタレジスタの指しているメモリの内容を参照するときは、レジスタ名を半角の丸かっこで包みます。
後は説明するまでもないでしょう。loaddataもgettotalも前回の第1案と同じ昇順に処理しています。
/*** addition by assembly language (asmadd_3.s) ***/
.equ ELEMENT, 0x0064 /* == '#define ELEMENT 100' */
/* -- local memory for 'shortary' -- */
.lcomm shortary, (0x02 * ELEMENT) /* 2 for each element */
.globl loaddata
.type loaddata, @function
loaddata:
mov $0x0001, %dx /* initialize data */
mov $ELEMENT, %bx /* initialize thelimit */
mov $shortary, %edi /* initialize pointer */
loaddata_l:
mov %dx, (%edi) /* store data in element */
inc %dx /* data++ */
add $0x0002, %edi /* to next element */
cmp %dx, %bx /* try (thelimit - data) */
jnc loaddata_l /* loop if no borrow */
ret
/* -- function gettotal -- */
.globl gettotal
.type gettotal, @function
gettotal:
mov $0x0000, %ax /* initialize answer */
mov $ELEMENT, %bx /* initialize thelimit */
mov $0x0000, %cx /* initialize counter */
mov $shortary, %esi /* initialize pointer */
gettotal_l:
add (%esi), %ax /* answer += element */
inc %cx /* counter++ */
add $0x0002, %esi /* to next element */
cmp %cx, %bx /* try (thelimit - counter) */
jnz gettotal_l /* loop if not zero */
ret
それではコンパイル、実行させてみましょう。大迂回作業、まことにお疲れさまでした。マシンは予想どおり答えの13baを表示しましたね。
(注1)
このへんの記述はウソではありませんが、ちょっと手抜きでした。興味のある読者のために、立ち入った解説をしておきましょう。
データの部分は狭い意味のデータの部分と、bssと呼ばれる部分に分かれています。前者は"Hello, world!"の文字列のように初期値が与えられている部分、後者は今回のshortaryのように初期値が与えられていない部分です。後者は実行可能ファイルに含める必要はなく、プログラムを実行するときに必要なメモリを確保するという仕組みになっています。
本文ですぐ次に出てくる「.lcomm shortary, (0x02 * 0x0064)」は、このbssにメモリを確保します。
(注2)
アセンブラasのマニュアルはあまり親切でないので、私はデータの部分を含む簡単なC言語のプログラムを作り、例の
gcc -S datatest.c
でアセンブリ言語を吐かせて参考にしました。ささやかですが読者の参考になるノウハウかも。
(注3)
ポインタレジスタの先頭アドレスをCSにして機械語のプログラムを操作する、すごい荒業をご披露しましょう。
プログラムの中にほとんど同じ(少しだけ違う)2つの関数があるとします。プログラムの実行中に機械語をチャチャッと変えてやれば、あらふしぎ、関数が別の関数に変身するので、関数はひとつで済むことになり、大幅にメモリを節約できます。
現役プロの読者には決してお勧めしませんよ
(注4)
システムが楽屋裏で小さなメモリを集めておいて、表舞台では「はいどうぞ、連続したメモリですよ」と渡す技術です。これは読者の方が詳しいでしょう。
(注5)
siとdiはアドレスを入れるだけのレジスタみたいな説明になって、少し気の毒でした。両者は四則演算をこなすなど、けっこう汎用レジスタに近い能力を持っています。