T型の配列

「ポインタ虎の巻」を読んでいたら、今更ながら、こんな盲点に気づかされた。

/* !!! 間違ったコード例 */
int data[3][5];
int **p;

p = data;        /* Syntax Error !!! */

二次元配列の値を、ポインタのポインタ変数に代入しているので、なんとな〜く正しそうな気はするのだけど、メモリの絵を描いてじっくり考えてみれば、これではコンパイルエラーが出て当然だということに気づく。言われればそうだなーと分かるのだが、C言語に少し慣れてくると、つい気を許してフィーリングに頼ってしまい、初歩的なうっかりミスを犯すことになるから気をつけたいな。

まず、基本から。
式中における配列のシンボル名は、「配列の先頭要素へのポインタ値」に展開される。例えば、「int data[5]」と宣言した場合、式中の配列シンボル「data」は、以下マークがついた要素へのポインタに展開される。

data
+-------+-------+-------+-------+-------+
| <int> | <int> | <int> | <int> | <int> |
+-------+-------+-------+-------+-------+
0       4       8       12      16      20    offset
 *******

そして、二次元配列の場合はどうかというと…

ここではあえて、多次元配列という概念は脇において、「T型の配列」として捉えてみる。

例えば、「int data2[3][5]」という宣言は、「「int型オブジェクトx5で構成された配列」を3要素持つ配列」であるが、これは「T=[5]」とする「T型の配列」である、と言い換えると分かりやすい。intやcharと同様、「配列」もデータ型のひとつであることに変わりない(正確に言うと派生型であるが)。

data2
+------------+------------+------------+
|  <int>[5]  |  <int>[5]  |  <int>[5]  |
+------------+------------+------------+
0           20            40
 ************

したがって、式中での配列のシンボル「data2」は、上でマークしたオブジェクトへのポインタに展開される。先頭の[5]を指すポインタである。

冒頭に戻ると、以下のコード片における「data」は、[5]へのポインタに展開されるため、pは「int型配列へのポインタ変数」として宣言する必要がある。

/* (^ ^)正しいコード例 */
int data[3][5];  /* 'data' is an array of <array of int> */
int (*p)[5];     /* 'p' is pointer to an <array of int> */

p = data;        /* OK */

今まで、「配列の先頭要素へのポインタ」という表現を使ってきたけど、多次元配列のことを視野に入れて言い直すと、

式中における配列シンボルは、最上位次元の配列の先頭要素へのポインタ値に展開される。
                            ^^^^^^^^^^^^

多次元配列は「T型の配列」として捉えると綺麗に整理されるよ、というお話でした。