変数はレジスタ、配列はバッファメモリだよね。

こんなコードを書いてみるよ。

#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 レジスタという、インテルの歴史的アーキテクチャの怨念なの。許してあげて。;><