変数はレジスタ、配列はバッファメモリだよね。
こんなコードを書いてみるよ。
#include#include static int a[4] = {1, 2, 3, 4}; int main(void) { int *p; p = &a[0]; return *p; }
で、アセンブリにしてみるよ。なにが出るかな。なにが出るかな。
$ gcc -O0 -S pointer-array.c $ cat pointer-array.s .file "pointer-array.c" .data .align 4 .type a, @object .size a, 16 a: .long 1 .long 2 .long 3 .long 4 .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp movl $a, -8(%ebp) movl -8(%ebp), %eax movl (%eax), %eax addl $16, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret
切り刻んで読んでいくよ。
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp
ここまではお約束。スタックフレームの準備とか色々してるらしいよ。
movl $a, -8(%ebp)
$aは{1, 2, 3, 4}のある場所だね。-8(%ebp)だからebp-8な所にコピーされたよ。多分そうだよね。
movl -8(%ebp), %eax
あれれ。今度は括弧がソースについてる。括弧がついているとレジスタに入っている値をアドレスと解釈して、そのアドレスにある値を拾ってきてコピーするよ。つまりeax = ebp-8だね。
movl (%eax), %eax
括弧がついているとレジスタに入っている値をアドレスと解釈(ry。あれ?それってポインタ変数にスター(*)をつけたのと同じことだよね。つまり、%eaxがポインタ変数p、(%eax)がポインタ変数pの実体*pってことだよね。movl (%eax), %eax。なんかカッコいいね。
movl $a, -8(%ebp) movl -8(%ebp), %eax
それにしても、なんかここ無駄だよね。魔法を唱えるよ。
#include#include static int a[4] = {1, 2, 3, 4}; int main(void) { register int *p; p = &a[0]; return *p; }
int *pの宣言の前にregister呪文を追加したよ。なにが出るかな。なにが出るかな。
$ gcc -O0 -S pointer-array.c $ cat pointer-array.s .file "pointer-array.c" .data .align 4 .type a, @object .size a, 16 a: .long 1 .long 2 .long 3 .long 4 .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx movl $a, %eax movl (%eax), %eax popl %ecx popl %ebp leal -4(%ecx), %esp ret
-8(%ebp)がなくなっちゃった。きっちりしたコードは気持ちいいね。うっとり。
movl $a, %eax movl (%eax), %eax
アッー!配列a[0]のベースアドレスが直接eaxに入って、(%eax)でa[0]の値を拾ってるよ。やっぱり
p | %eax | |
*p | (%eax) |
だね。ポインタ変数って、つまりメモリアドレスを格納したレジスタそのものなんだね。
でも、ポインタ変数が全部レジスタにしか置き換えられないとしたら、大変なことが起きるよね。だって、関数の中でCPUが持っているレジスタの数だけしかポインタ変数が使えなくなっちゃう。そんなの嫌だよね。
movl $a, -8(%ebp) movl -8(%ebp), %eax
一見無駄に見えたこの-8(%ebp)が、実はとても偉い人だったの。ポインタ変数が2個3個と増えたら、-12(%ebp), -16(%ebp)と増やすだけでアラ不思議。いっぱいポインタ変数が使えるよ。うれしいね。
しかも、ポインタのポインタとか、リンクトリストとかアセンブリで書いたら発狂しそうな難しいことも簡単にできちゃう。うはっwww夢がひろがりんぐwww
つまり、Cコンパイラはソフトウェアを書く人にとても親切なんだね。C言語とコンパイラを作ってくれた先人たちに感謝しなきゃ。足を向けて眠れないね。
そうそう。配列。static int a[4] = {1, 2, 3, 4};は
a: .long 1 .long 2 .long 3 .long 4
こうなった。配列はメモリ上に場所を予約してくれるんだよ。ついでに初期化もしてくれる。親切だね。
実際の動作を見てみたいなら、アセンブルしてDDDを使うとお手軽だよ。gdbのコマンド、ややこしいからね。;><
$ gcc -g -o pointer-array pointer-array.s $ ddd ./pointer-array
⇒ 拡大
ところで。それにしても、ここ、ナゾだよね。
movl $a, -8(%ebp) movl -8(%ebp), %eax
違和感を感じたアナタ。鋭い。センスあるよ。括弧()の使い方がなんか違うよね。これは、ソースは即値、デスティネーションはアドレス or レジスタという、インテルの歴史的アーキテクチャの怨念なの。許してあげて。;><