第10章 続・ポインタ天国

 多次元配列もメモリ上にあります。アドレスがあります。アドレスはただの数値です。なら、多次元配列のアドレスを入れておく変数があったっていいじゃないですか。今回はそういうお話です。


 では、今回の要点です。


 では、いってみましょう。


 というわけで、今回は多次元配列のアドレスを入れるポインタのお話です。先ずは2次元配列から考えていきます。

 先ず、int array[5][5]; があるとします。この2次元配列のアドレスをポインタに保存したいと思います。そこで、先ずは1次元配列の時と同じようにしてみましょう。

int* parray = array;
'initializing' : 'int [5][5]' から 'int *' に変換することはできません。

 ...どうやらエラーが出るようです。このように、多次元配列のアドレスは、普通のポインタには入れることはできません。それ用のポインタが必要になるのです。


 さて、ここで2次元配列の初期化(第1部第62章参照)を思い出してみましょう。

int array[][5] = {
    {  1,  2,  3,  4,  5, },
    {  2,  4,  6,  8, 10, },
    {  3,  6,  9, 12, 15, },
    {  4,  8, 12, 16, 20, },
};

 最初の要素数を省略しましたね。

 1次元配列へのポインタ(普通のポインタ)は「この要素数の省略できるカッコをのけて、変数名の先頭に * をつけたものだ」と解釈することができます。ということで、2次元配列のアドレスを入れるポインタもこれと同じように考えてみましょう。

int* parray[5];

 ...これでは int* 型の配列になってしまいますね。

 では、前回の関数ポインタの時と同じようにカッコで分断してしまいましょう。

int (*parray[5]);
int (*parray)[5];

 ...2通り考えられますね。

 では、こう考えてみましょうか。2次元配列のアドレスを入れるポインタの配列はどうなるのでしょうか? 配列変数を作るときは、変数名の後ろに [ ] をつけるのでしたね。では、上の変数名の後ろにそれぞれ [4] をつけてみましょう。

int (*parray[4][5]);
int (*parray[4])[5];

 前者では2次元配列のアドレスを入れるポインタの配列なのか、ポインタの2次元配列なのか、はたまた3次元配列のアドレスを入れるポインタなのか全く区別がつきませんね。ということで、後者が正しいのです。

 つまり、2次元配列のアドレスを入れるポインタは

int (*parray)[5];

のようになるわけです。[5] というのがついていますが、これは1変数です。配列変数ではありません

 このようにしておくと、先ずはアドレスの初期化は

parray = array;

のようにでき、そしてこのポインタを使うときは

parray[0][0] = 0;

のように、普通に使うことができます。


 関数の仮引数の場合はもっと簡単になります。

 第1部第35章で、1次元配列を受け取る仮引数の場合は、

void Func(int* parray)

void Func(int parray[])

どちらもポインタになると言いました。

 これは2次元配列でも同じようにできます。

void Func(int (*parray)[5])

void Func(int parray[][5])

と書いても同じことになるのです。

 これは3次元配列以降でも同じようにできます。3次元配列では

void Func(int (*parray)[5][10])
または
void Func(int parray[][5][10])

4次元配列では

void Func(int (*parray)[5][5][5])
または
void Func(int parray[][5][5][5])

となりますね。


 さて、この多次元配列のアドレスを入れるポインタですが、いちいち2番目以降の要素数を指定しないといけないのはなぜなのでしょうか?

 そもそも、1次元配列を普通のポインタで扱えていたのは、「先頭のアドレスと変数の型さえわかっていれば a[1] や a[2] 、果ては a[999] のアドレスまでわかってしまう」からでした(第1部第35章参照)。

 しかし、2次元配列はそれだけでは不十分なのです。簡単な例で考えてみましょう。

int array[2][3];

 ...無茶苦茶簡単ですね。これはメモリ上で array[0][0], array[0][1], array[0][2], array[1][0], array[1][1], array[1][2] の順番で並んでいます。

要素 array[0][0] array[0][1] array[0][2] array[1][0] array[1][1] array[1][2]
アドレス(実例) 0x0065FDE8 0x0065FDEC 0x0065FDF0 0x0065FDF4 0x0065FDF8 0x0065FDFC

 そうです。こういう感じです。

 さぁ、例えばこの array[1][2] のアドレス 0x0065FDFC を、先頭アドレス 0x0065FDE8 と、変数の型 (int) のサイズ4、そしてインデックス1、2とからだけで求めてみましょう。あなたならどうしますか?

0x0065FDE8 + sizeof (int) * (1 * 3 + 2) = 0x0065FDFC

 おや? 「3」なんて数がありますね。これはルール違反ですよ? とは言っても、こうしなければアドレスの計算は不可能です。1 * 3 を 1 + 1 + 1 にしたとしても、この「3回足す」という根拠がどこから来たのか分かりません。結局は同じことです。

 多次元配列のアドレスを入れるポインタでいちいち2番目以降のサイズが必要なのは、このせいです。この「3」はまさに2番目の要素数ですね。


 このことは、別の観点から考えることもできます。

 第1部第62章で「2次元配列は配列の配列だ」と言いました。よって、2次元配列の要素は「配列」だと考えられます。

 すると、int array[2][3]; の要素は「int 型3要素の配列」になります。つまり、array のアドレスを格納するポインタは「int 型3要素の配列へのポインタ」になります。それが

int (*parray)[3];

なわけですね。

 typedef を交えて考えてみましょう。

 「int 型3要素の配列」に対する型を作ってみましょう。

typedef int ARRAY3[3];

 すると、int array[2][3]; は

ARRAY3 array[2];

 だと考えられます。あとは普通の配列の時と同じように、

ARRAY3* parray = array;

 とできるわけです。sizeof *parray 、つまり sizeof (ARRAY3) は12になるので、これで先頭アドレスと型のサイズとインデックスだけから各要素の位置を知ることができますね。

 int (*parray)[3]; のときも、sizeof *parray は12になります。このことから分かるように、2次元配列のアドレスを入れるポインタは、実は「1次元配列へのポインタ」だったのです。


 いろいろ考え方はありますが、このように多次元配列のアドレスを入れるポインタには2番目以降の要素数が必須なのです。


 では、今回の要点です。


 次回からは話を変え、構造体関係の話をしてみようと思います。それでは。


第9章 関数ポインタ | 第11章 一心同体

Last update was done on 2000.8.6

この講座の著作権はロベールが保有しています