苦しんで覚えるC言語

変数とメモリの関係


変数はメモリ上に存在する


第5章での変数の説明時に、変数はメモリに作られると説明しました。
しかし、その具体的な内容については全く説明してきませんでした。
それは、変数を扱うだけなら、そこまで知る必要がなかったからです。

しかし、この先で取り扱うポインタという概念を理解するためには、
変数がどのようにメモリに格納されているかを知っておく必要があります。

前節で説明した通り、コンピュータのメモリとは、巨大な1列ロッカーのような構造です。
そして、このロッカーは、1つ1つがオンとオフの2つの状態を記憶しています。
このロッカー1つを1ビット、8つをまとめて1バイトと呼んでおり、
メモリには、この1バイト毎に番号がつけられて区別されているのです。

プログラムで宣言した変数も、このメモリに番号付きで記憶されています。
ただ、番号で区別するのではわかりにくいので、名前をつけているのです。
実行ファイルにコンパイルすると、変数名は番号に変換されます。

要するに、全ての変数はメモリに作られており、
そこでは、番号をつけて区別しているということをまずは頭に入れて下さい。
C言語はこんな言語である
以後、この章では、C言語の実態に迫る展開が多々見られます。
今までの章で説明されたなかったことの多くがこの章で明らかにされます。
その中では、C言語の文法などを批判するような展開も多く見られます。

そもそも、C言語は、デニス・リッチーという人物が、
自分達で使うためだけに作ったプログラミング言語であって、
世界中で使われるようになるとは全く考えていなかったのです。
その為、作った人の都合に合わせたような部分が見られ、
普通の人々には不親切な文法の言語になっています。

しかし、それでも、C言語は世界中で使われているのです。
所々不可解な部分はありますが、最も汎用性のある言語であり、
小さな部品から、プレステのゲームまで、幅広く使用されています。

目次に戻る

メモリ上の番号を表示する


前項では、全ての変数はメモリに作られて、番号をつけて区別していると説明しました。
実は、変数につけられた番号は、プログラムで調べることが出来るのです。

番号を調べるのは意外に簡単で、printf関数で%p指定子を使うだけです。
ただし、変数名の前には、&をつける必要があります。
次のプログラムは、int型変数iの番号を表示する例です。

#include <stdio.h>

int main(void)
{
	int i;
	printf("%p\n",&i);
	return 0;
}
このプログラムの実行結果は、次のようになるかもしれません。

0012FF80
これは筆者の環境で実行した結果ですが、結果の数字は、
パソコンや使用するコンパイラによって異なります。
指定された変数をどの番号のメモリに割り当てるのかは、
コンパイラ(正確にはリンカ)が自動的に決めてしまい、
更にその値もOSの仮想メモリ機能によって変化するからです。

この番号は16進数という表示方式です。10進数にすると 1245056 となります。
何故、16進数のようなわかりにくい方法で表示されているのかと言いますと、
前項で説明した通り、コンピュータの数値は2進数で記憶されています。
 2進数では、 2、4、8、16、・・・・と桁があがっていきます。
10進数では、10、100、1000、・・と桁があがっていきます。
16進数では、16、256、4096、・・と桁があがっていきます。
これを見ると気づくかもしれませんが、10進数と2進数はうまくかみ合いません。
16進数は、2進数とうまくかみ合うため、都合が良いのです。
コンピュータは何故2進数?
突然ですが、あなたは両手の指10本でいくつまで数を数えられますか?
普通は10までですが、実は、理論的には1024まで数えられます。
指一本を2進数1桁に対応させて数えればいいのです。
この様に、10進数よりも2進数で数えるようにした方が、
回路を小型・単純にすることが出来ます。
とはいえ、ここでは、16進数うんぬんはさほど重要なことではありません。
重要なことは、変数につけられた番号を簡単に調べることが出来る点です。
この、変数につけられた番号のことを、アドレスと呼んでいます。
アドレス
変数につけられたメモリ上での番号のこと。
これは、URLアドレスと言うようなアドレスと全く同じ意味と考えて問題ありません。
変数の住所を示す番号が、すなわちアドレスなのです。

目次に戻る

複数の変数の番号


前項では、1つだけしか調べなかったので、今回は複数の変数のアドレスを調べてみます。
繰り返しますが、変数につけられたロッカー番号のことを、アドレスと呼んでいます。
次のプログラムは、3つのint型変数のアドレスを表示する例です。

#include <stdio.h>

int main(void)
{
	int i1,i2,i3;
	printf("i1(%p)\n",&i1);
	printf("i2(%p)\n",&i2);
	printf("i3(%p)\n",&i3);
	return 0;
}
このプログラムの実行結果は、次のようになるかもしれません。

i1(0012FF78) 10進数では1245048
i2(0012FF7C) 10進数では1245052
i3(0012FF80) 10進数では1245056
これは筆者の環境で実行した例です。数値は環境によって異なります。
どうやら、int型変数には4番ずれの連番が割り当てられているようです。
これは、int型のサイズが4バイトだからに他ありません。
ただし、LSI C-86では2バイトになります。

4バイトとは、4x8=32なので、32ビット、2進数32桁ということになり、
+-を考えない場合、約43億までの数値を記憶することが出来ます。
2バイトの場合は、65535までとなります。

int型以外の変数もコンパイラ毎にサイズが異なっています。
ただし、char型だけは、必ず1バイトと決まっています。
char型には決まった範囲の番号が使われた文字を入れておくために、
同じサイズにしておかないと何かと不都合だからです。

今回は連番になりましたが、変数のアドレスはいつでも必ず連番とは限りません
何故なら、この3つの変数は別々に使われる変数なので、
バラバラの場所にあっても何も問題はないからです。
コンパイラによっては逆順になったりすることもあります。

目次に戻る

配列の番号


変数の番号同様、配列もアドレスを表示することが出来ます。
しつこいですが、アドレスとは、変数につけられたロッカー番号のことです。
配列の場合には、先頭に&をつける必要はありません。
ただし、配列の個々の要素は変数と同じ扱いなので、当然&をつけます。
次のプログラムは、配列とその要素のアドレスを表示する例です。

#include <stdio.h>

int main(void)
{
	int array[10];
	printf("array___(%p)\n",array);
	printf("array0\n",&array[0]);
	printf("array1\n",&array[1]);
	printf("array2\n",&array[2]);
	return 0;
}
このプログラムの実行結果は、次のようになるかもしれません。

array___(0012FF5C) 10進数では 1245020
array[0](0012FF5C) 10進数では 1245020
array[1](0012FF60) 10進数では 1245024
array[2](0012FF64) 10進数では 1245028
どうやら、配列の場合も、4バイトずつ連番に割り当てられているようです。
更に、配列名を指定した場合、配列の最初の要素と同じ番号になるようです。

実は、ここに、配列のトリックの全てが暴かれています。
実は配列名は、配列の最初の要素のアドレスを表していたのです。
各要素を参照する時に、[0],[1] と言った要素番号をつけましたが、この意味は、
配列名のアドレス+要素番号のメモリを参照するという意味になるのです。

つまり、最初のアドレスを決めておけば、それに番号を足し算することで、
たくさんの変数が並んでいるという状態を表現することが出来るのです。

ただ、ここで少し疑問なのは、要素番号1の時、実際のアドレスは4増えていることです。
int型のサイズを思い出していただければすぐにわかります。
このコンパイラでは、int型のサイズは4バイトです。
つまり、1つ先のint型の変数のアドレスは、最初のアドレス+4となるのです。
同様に、2つ先のint型の変数のアドレスは、最初のアドレス+8となります。

この配列の仕組みについては、後ほど更に突っ込んだ説明を行います。

目次に戻る