読者です 読者をやめる 読者になる 読者になる

わらばんし仄聞記

南の国で引きこもってるWeb屋さん

原書で学ぶ64bitアセンブラ入門(5)

10章の配列について。
原書で学ぶ64bitアセンブラ入門(5)でやった内容になります。

chapter10 Arrays

Array address computation

配列とは、つまりが、ある特定の型(byte, words, double-words, quad-wordsなどのいずれか)のメモリ領域の連なりです。つまり、各要素の占めるバイト数は同じであり、各要素のアドレスは等間隔となるので容易に計算できます。
たとえば、アドレスbaseから始まる1要素mバイトの配列aについて考えてみると、要素a[i]base+i*mに位置します。
以下のコードを使った場合、aは1要素がbyte長で100要素の配列先頭アドレス、bは1要素がdouble word長で100要素の配列先頭アドレスといった具合になります。

General pattern for memory refereces

メモリ参照の一般的なパターンについて。
よくあるパターンとしては以下のものがあります。

パターン 説明
[label] labelの位置に含まれる値
[label+2*ind] ラベルにインデックスレジスタの2倍を加えた値のメモリアドレスから得られる値
[label+4*ind] ラベルにインデックスレジスタの4倍を加えた値のメモリアドレスから得られる値
[label+8*ind] ラベルにインデックスレジスタの8倍を加えた値のメモリアドレスから得られる値
[reg] レジスタにあるメモリアドレスの位置にある値
[reg+k*ind] レジスタにインデックスレジスタのk倍を加えた値のメモリアドレスから得られる値
[label+reg+k*ind] ラベル、レジスタ、インデックスレジスタのk倍を加えた値のメモリアドレスから得られる値
[number+reg+k*ind] 数値、レジスタ、インデックスレジスタのk倍を加えた値のメモリアドレスから得られる値

[label][reg]についてはそのままなので特に問題ないでしょう。
[label+2*idx]といった形式を使うと、この場合ならidxが増える毎にアドレスが2つ進み、1要素がword長の配列に対してこれを使うならidxの値が配列のインデックスとしての意味を持つことになります。
レジスタを使っている[reg]は、配列を関数へ渡す場合、そのアドレスをレジスタへ配置しなければならない為、そういった用途の場合にはこちらを使うことになります。
実際にこの挙動を試しているコードを本書から引用したものが以下になります。

2行目でdataセグメントに1要素がdouble-word長として5要素もつ配列aを定義し、4行目でdouble-word長で10要素分の領域をbとして確保しています。
10,11行目でcopy_array関数へ配列a,bを渡すための準備としてレジスタへ各配列のアドレスをセットしています。edxにはコピーする要素数の数値をセットしてcopy_array関数をcall。この関数内では配列の1要素ずつをループしてコピーするため、18行目でカウンタとして使うrcxレジスタを0クリア。
20,21行目が肝となる配列要素のコピー箇所で、先に0クリアしたカウンタとして働くrcxレジスタが先に述べたインデックスレジスタとして機能しているのが見て取れます。

表にあった[number+reg+k*ind]なんかは構造体の配列を操作するのに使えます。reg,k,indは今までと同様、配列要素を示すのに使い、更にnumberが配列要素である構造体の何処を示すかのオフセットとして使います。

Allocating arrays

アセンブリでも動的にメモリを割り当てる単純な方法としては、malloc関数を使います。mallocで返されるメモリ領域は、16バイトでalignされた境界を持ちます。
使用例はこんな感じ

extern  malloc
…
mov     rdi, 1000000000
call    malloc
mov     [pointer], rax

動的にメモリ領域を取得する動機としては、静的に確保する場合は予め領域のサイズを指定しておかなければならないため、通常、十分に余裕のあるだけの領域を確保しておくことになります。なので、大抵の場合、余裕の分だけ未使用の領域が出てくるので非効率です。
また、dataセグメントで確保するように定義されていると、実行ファイル自体にもその配列分の領域を確保する必要があるため、サイズが無駄に大きくなってしまいます。