ポインタの読み方

C を勉強し始めたとき真っ先に躓くのがポインタです。

int a; の宣言で a が整数の変数であるというのは分かりやすいのですが、int *b; では b は整数ではなく *b が整数であるようなのです。そうして b は整数を指し示す矢印のようなもので、おまけに &a は整数が納められているアドレスだそうです。

さらにキャストと言うものがあって、(char *)p はポインタ p を char 型のデータのポインタに読みかえるようです。そのあたりでも充分混乱しますが、その上に **p のようなポインタのポインタというのも出現します。それらは組み合わせが効くようで *(char **)p のようになってくるともう逃げだしたくなります。そういうわけで、ポインタ嫌いの C プログラマがいてもうなづける話です。

この複雑怪奇なポインタについては図解して説明してある本もあるようですが、矢印がこんがらがってあまり頭の混乱を鎮める役には立たないようです。

ポインタがこのように分かりにくいのはそれを日本語に訳すことができないからではないでしょうか。printf 関数などは「印刷する」と日本語にするとなんとなく分かったような気になります。しかし *(char **)ptr のようなものは到底翻訳できそうにありません。

そこで思い切って大胆に割り切ってポインタを無理矢理日本語に訳してみることにしました。ルールは実に簡単です。二つしかありません。第一は、char *ptr; のような宣言や (char *)ptr のようなキャストや &ptr のようなアドレス演算子は全て「文字のポインタたる」という ptr の役職を表す肩書きと考えることにします。つまり上のどの表現があらわれても ptr のことを 「文字のポインタたる」ptr と呼ぶことにします。「内閣総理大臣たる」**** というのと同じことです。したがって「... のポインタたる」という肩書きがでてきたら ptr (& の場合は &ptr) はポインタだなと考えるのです。

第二のルールは *ptr という表現がでてきたとき * のことを「... の中味」と呼ぶことにします。つまり *ptr は「ポインタ ptr の中味」と呼ぶことにするのです。

ルールはこの二つだけです。随分割り切って考えているようですが、これが結構役に立ちます。たとえば先ほどの例の *(char **)ptr を考えてみましょうこれは「char のポインタのポインタたる ptr 」ですから ptr はポインタです。そうすると先頭の * は「中味」なので結局 *(char **)ptr は「char のポインタのポインタたる ptr の中味」ですから単なる char のポインタであることが分かります。

何となく腑に落ちない人も多いと思いますので次の例を見てみましょう。これはこのページで紹介した文字列を検索するプログラム strsort.c の中の文字列の比較をする関数です。

int comp( const void *p1, const void *p2 )
{
        return strcmp( (char *)p1, *(char **)p2 );
}

この関数はいろいろな型の引数を扱わなくては行けないので引数が void 型のポインタとして comp 関数に渡されます。void 型のポインタを受け取った comp 関数側はそれを目的に会うように様々な型のデータのポインタにキャストして利用するわけです。これを上で説明した二つのルールで解読してみましょう。

まず comp 関数の二つの引数についてですが二つとも void *p1 のような形をしています。したがって、「void のポインタたる」p1 ですから p1 はポインタです void 型のポインタですからまだ具体的なデータを指し示すわけではなく、いわば、白紙のポインタです。また const は p1 と p2 の示すデータの内容が書き換えできないようにするための宣言ですから、引数としてコピーしてきた p1 と p2 自身の加工は可能です。

つぎに strcmp の引数を見てみましょう。strcmp は string.h で宣言されている関数で引数に二つの文字列のポインタを取り両者の中味(中味と言うより指し示すと言った方がよいと思われる場合はそれでもよいと思います)である文字列の比較を行います。strcmp の第一引数は (char *)p1 ですがこれは白紙の p1 を「char のポインタたる」p1 であると言っています。したがって p1 はポインタでかつ char 型のデータを指し示すポインタと言う事になります。

strcmp の第二の引数は *(char **)p2 です。(char **) は「char のポインタのポインタたる」p2 と読めます。したがって、p2 は char 型のデータを指し示すポインタを指し示すポインタです。先頭の * は「の中味」と呼びますから、*(char **)p2 は「char のポインタのポインタの中味」ですからこれも char 型のポインタとなって、strcmp の引数の条件を満たすことができます。また、p1 と p2 の引数のキャストがちがうのはp1 = "keyword" で p1 が文字列への直接のポインタであるのに対し、p2 = a[] で p2 は文字列の配列へのポインタだからです。

このように、「... たる」と「... の中味」と単純に二分する方法でポインタの性質が分かりやすくなるのではないでしょうか。

もっとも、K & R の「プログラミング言語C 第2版」にはもっときちんとした方法でポインタを普通の言葉に直す方法が書いてあります。... 誰かが C 言語の勉強はこの本一冊あれば充分だと書いていましたが、今度読み返してみたら本当にそうかも知れないと思いました。積ん読するには惜しい本です。