宣言文の記述

次に、「ポインタのポインタ」と、それに対応する「ポインタの配列」について考えてみましょう。いま、次のように宣言したとします。

char **pp;     ← 「char型へのポインタ」へのポインタppを宣言
char *ap[3];   ← 「char型へのポインタ」を要素とする配列ap(要素数3)を宣言

この結果を図にすると図3、図4のようになります。

図3 「char型へのポインタ」へのポインタppを宣言

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

「char **pp;」という宣言は、ポインタ(pp)が指し示す先(*pp)もポインタであり、そのポインタ(*pp)が指し示す先(**pp)がchar型であるという意味です。この宣言では、ポインタのpp自体のメモリ領域は確保されますが、その内容は初期化されておらず、*ppも、**ppもまだ存在しない状態となります。

一方、「char *ap[3];」という宣言は少し複雑です。C言語では、配列を表す[ ]は、ポインタの*よりも優先順位が高くなっています。よって、「char *ap[3];」という宣言は次のように分解して考えることができます。

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

apは配列の先頭アドレスを表す定数で、配列要素ap[x]を求めることができる

ap[x]はポインタであり、その指し示す先の「*ap[x]」はchar型である

以上により、「char *ap[3];」が「ポインタの配列」を宣言していることが理解できるはずです。ここでは、アドレス定数apが定義され、同時に、配列要素である「char型へのポインタ」3個分のメモリ領域が確保されます。しかし、そのポインタの内容は初期化されておらず、その指し示す先の*ap[0]などのメモリ領域はまだ存在しない状態となります。

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

「char **pp;」や「char *ap[3];」を宣言したあとは、式の中ではppに対してpp[2][3]のように記述したり、apに対して**apのように記述したりすることが可能です。もちろん、各ポインタには有効なアドレスが代入されていることが前提です。

以上を踏まえてリスト2を実行すると、printf()のところでいずれも文字「Y」が表示されるはずです。なお、[ ]と*とでは[ ]が優先されるため、適宜、優先順位指定の括弧が必要なことに注意してください。

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

  char **pp;     ← 「char型へのポインタ」へのポインタppを宣言
  char *ap[3];   ← 「char型へのポインタ」を要素とする配列ap(要素数3)を宣言
  char a[5];     ←  char型を要素とする、要素数5の配列aを宣言

  ap[2] = a;                    ← ap[2](ポインタ)にaのアドレス定数を代入
  a[3] = 'Y';                   ← a[3]には文字'Y'を代入
  pp = ap;                      ← apのアドレス定数はポインタのポインタに代入可
  printf("%c\n", ap[2][3]);     ← apに[ ]の添字を2つ付けて内容を読む
  printf("%c\n", pp[2][3]);     ← ppを使っても同様に記述できる
  printf("%c\n", *(*(ap+2)+3)); ← ポインタとして記述する場合はこうなる
  printf("%c\n", *(*(pp+2)+3)); ← ppを使う場合も同様
  printf("%c\n", *(ap[2]+3));   ← 添字の[ ]とポインタの*が混在した記述も可能
  printf("%c\n", *(pp[2]+3));   ← ppを使う場合も同様
  printf("%c\n", (*(ap+2))[3]); ← 添字の[ ]とポインタの*を入れ替えた場合
  printf("%c\n", (*(pp+2))[3]); ← もちろんppを使っても同様

ここで、ap[2][3]という記述は、あたかも後述の2次元配列のように見えますが、apはあくまで「char *ap[3];」と宣言された「ポインタの配列」であり、2次元配列とは異なることに注意してください。

関数の仮引数の宣言では

関数の仮引数には、実引数が「ポインタの配列」であっても、「ポインタのポインタ」に変換されて「値渡し」されます。したがって、次の関数の仮引数の宣言は、どちらも同じ意味になります。

void func(char **pp)    ← 仮引数を「ポインタのポインタ」として宣言
void func(char *pp[])   ← 仮引数を「ポインタの配列」として宣言

この「char **pp」型の仮引数は、main()関数のargvと同じ型であり、argvは次のどちらで宣言しても同じ意味になることからも理解できるはずです。

int main(int argc, char **argv)
int main(int argc, char *argv[])