宣言文の記述

次のように宣言すると、「配列へのポインタ」と「2次元配列」ができます。

char (*pa)[5];  ← 「char型を要素とする配列(要素数5)」へのポインタpaを宣言
char aa[3][5];  ← char型を要素とする、3×5の2次元配列aaを宣言

この宣言の結果は、図5、図6のとおりです。

図5 「char型を要素とする配列(要素数5)」へのポインタpaを宣言

図6 char型を要素とする、3×5の2次元配列aaを宣言

ここで、「配列へのポインタ」の宣言の「char (*pa)[5];」は、次のように考えます。

あるpaという名前のものがある

paはポインタであり、paが指し示す先(*pa)は、何らかの要素数5の配列全体である

その配列の各要素「(*pa)[x]」はchar型である

「配列へのポインタ」は、単に配列の先頭アドレスを指したポインタではありません。このポインタ(pa)は所定の大きさの配列全体を指しており、paをインクリメントすると、paが指し示す配列のサイズ分(5バイト)、アドレス値が加算されます。このため、pa自体はポインタであっても、宣言時にはそのポインタの指し示す先の配列の大きさを指定する必要があります。

なお、paの宣言では、[5]という値を指定していますが、実際の配列用のメモリは(宣言しただけでは)確保されません。

一方、2次元配列「char aa[3][5];」は、アドレス定数aaが示す先頭アドレスから順に3×5個分のchar型のメモリ領域が連続して確保されます。C言語の2次元配列は、「配列の配列」とも考えることができます。この場合、aa[0]、aa[1]、aa[2]という3つの要素を持った配列があり、その要素自体がさらに別の配列になっていて、たとえばaa[0]は、aa[0][0]、aa[0][1]…aa[0][4]までの5つの要素を持っていると解釈するのです。

式の中(プログラム本体)では

式の中では、2次元配列の配列名は、「配列へのポインタ」に代入できます。ただし、宣言時において「配列へのポインタ」の配列の要素数が、2次元配列の右側(下位側)の配列の要素数と一致している必要があります。具体的には、2次元配列が「char aa[3][5];」と宣言されている場合、右側の要素数が5であるため、「配列へのポインタ」は「char (*pa)[5];」と宣言する必要があるということです。

以上を踏まえたサンプルプログラムがリスト3です。このプログラムでは、すべてのprintf()の箇所で、文字'Z'を表示するはずです。

リスト3 「配列へのポインタ」と「2次元配列」を使ったサンプルプログラム

  char (*pa)[5];  ← 「char型を要素とする配列(要素数5)」へのポインタpaを宣言
  char aa[3][5];  ← char型を要素とする、3×5の2次元配列aaを宣言

  aa[2][3] = 'Z';               ← 2次元配列aaの要素[2][3]に文字'Z'を代入
  pa = aa;                      ← 2次元配列の配列名aaは、ポインタpaに代入可能
  printf("%c\n", aa[2][3]);     ← 普通に2次元配列として内容を読む
  printf("%c\n", pa[2][3]);     ← paを使っても2次元配列のように記述できる
  printf("%c\n", *(*(aa+2)+3)); ← ポインタとして記述する場合はこうなる
  printf("%c\n", *(*(pa+2)+3)); ← paを使う場合も同様
  printf("%c\n", *(aa[2]+3));   ← 添字の[ ]とポインタの*が混在した記述も可能
  printf("%c\n", *(pa[2]+3));   ← paを使う場合も同様
  printf("%c\n", (*(aa+2))[3]); ← 添字の[ ]とポインタの*を入れ替えた場合
  printf("%c\n", (*(pa+2))[3]); ← もちろんpaを使っても同様

リスト3は、paやaaの宣言部分は異なるものの、printf()の部分の記述は前述(リスト2)の「ポインタのポインタ」や「ポインタの配列」の場合とそっくりであることに注目してください。つまり、「ポインタの配列」と2次元配列とは、データ構造が異なるにもかかわらず、式の中ではどちらも同じように記述できてしまうということです。

関数の仮引数の宣言では

2次元配列の配列名を引数として関数を呼び出すことはあまりありませんが、そのような関数を呼び出すと、2次元配列の配列名は「配列へのポインタ」に変換されます。よって、関数の仮引数の宣言は以下の書式になり、どちらも同じ意味になります。

void func(char (*pa)[5])   ← 「配列へのポインタ」形式で仮引数を宣言
void func(char pa[][5])    ← 2次元配列形式で仮引数を宣言

前述のとおり、「配列へのポインタ」の配列の要素数は省略できず、2次元配列形式で宣言する場合でも、右側の添字の要素数は省略できません。

おわりに

以上、C言語のポインタについて、配列と対比しながら再考察を行いました。ポインタにはまだまだ考慮するべき点がありますが、ひとまず本稿がポインタの理解に役立てば幸いです。