最近、C プログラミングの初心者にポインターを説明する機会があり、次のような難しさに遭遇しました。ポインターの使い方をすでに知っている場合は、まったく問題には思えないかもしれませんが、次の例を冷静に考えてみてください。
int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);
まったくの初心者にとって、この出力は驚くべきものかもしれません。2 行目で *bar を &foo として宣言したばかりですが、4 行目で *bar は実際には &foo ではなく foo であることがわかります。
混乱の原因は、* 記号の曖昧さにあると言えるでしょう。2 行目では、* 記号はポインターを宣言するために使用されています。4 行目では、* 記号はポインターが指す値を取得する単項演算子として使用されています。これらは 2 つの異なるものですよね?
しかし、この「説明」は初心者にはまったく役に立ちません。微妙な矛盾を指摘することで新しい概念を紹介しているだけです。これは正しい教え方とは言えません。
それで、カーニハンとリッチーはそれをどう説明したのでしょうか?
単項演算子 * は間接演算子または逆参照演算子です。ポインターに適用すると、ポインターが指すオブジェクトにアクセスします。[…]
ポインタ ip の宣言は
int *ip
ニーモニックとして意図されており、式が*ip
int であることを示します。変数の宣言の構文は、変数が出現する可能性のある式の構文を模倣する。。
int *ip
*ip
は、「が を返す」と読むべきでしょうかint
? しかし、なぜ宣言後の割り当てはそのパターンに従わないのでしょうか? 初心者が変数を初期化したい場合はどうなりますか? int *ip = 1
(読み方:*ip
が を返しint
、 はint
です1
) は期待どおりに動作しません。概念モデルが首尾一貫していないようです。何か見落としているのでしょうか?
編集:それは試みた答えをここにまとめます。
ベストアンサー1
速記の理由:
int *bar = &foo;
あなたの例で混乱を招く可能性があるのは、これを次のものと同等であると誤解しやすいことです:
int *bar;
*bar = &foo; // error: use of uninitialized pointer bar!
それが実は手段:
int *bar;
bar = &foo;
変数の宣言と割り当てを分離して次のように記述すると、混乱が生じる可能性はなくなり、K&R の引用で説明されている使用 ↔ 宣言の並列処理は完璧に機能します。
最初の行は、である
bar
ような変数 を宣言します。*bar
int
2 行目は、 のアドレス
foo
を に割り当てbar
、*bar
( ) を(int
) のエイリアスにします。foo
int
C ポインタ構文を初心者に紹介する場合、最初はポインタ宣言と代入を分離するこのスタイルに固執し、C でのポインタ使用の基本概念が十分に理解された後にのみ、結合された省略構文を(混乱を招く可能性があるという適切な警告を付けて)紹介すると役立つ場合があります。