ポインタで使う記号は2つだけです。& と *。この2つの意味を取り違えないことが、最大の関門です。
& は「住所を調べる」
トラック10で使った演算子です。変数の前に付けると、その変数の番地が得られます。
int a = 5;
printf("%p\n", (void *)&a); // a の番地が出る日本語にすると「a はどこにいますか」。答えとして返ってくるのが番地です。
* は「その住所を訪ねる」
新しい道具です。番地の前に付けると、その番地の場所へ行って中身を取り出します。これを間接参照(デリファレンス)と呼びます。
int a = 5;
int *p = &a;
printf("%d\n", *p); // 5 が出るp の中身は番地ですが、*p と書いた瞬間に「その番地を訪ねた先にあるもの」、つまり a そのものになります。
& で住所を調べ、* でその住所を訪ねる。郵便屋の仕事はこの2つだけですのよ2つは打ち消し合う
& と * は逆向きの操作なので、続けて書くと元に戻ります。
int a = 5;
printf("%d\n", *&a); // 5(a の住所を調べて、そこを訪ねた)「a の住所を調べて、その住所を訪ねる」=「a」。当たり前ですね。この当たり前が腹に落ちていれば、ポインタの式は落ち着いて読めます。
動く様子を目で見る
宣言 → & で代入 → * で読む、という一番基本の流れをメモリの図で追ってみましょう。矢印が出た瞬間に注目してください。
p が作られたとき、中身は「?」でした。まだどこの番地も書いていない宛先メモです。つぎの行で &a の結果(図では 0x1000)が書き込まれ、はじめて矢印が引かれます。
いい着眼点です。矢印は絵の都合で、実際に線がつながっているわけではありません。p というロッカーの中に「0x1000」という数値が書いてあるだけです。ポインタは矢印ではなく数値、これは後で誤解トップ5にも登場します。
表示のしかたを整理する
3つを並べて確認しておきましょう。
int a = 5;
int *p = &a;
printf("%d\n", a); // 5 … 値そのもの
printf("%p\n", (void *)&a); // 0x... … a の番地
printf("%p\n", (void *)p); // 0x... … p の中身(=a の番地。同じ値)
printf("%d\n", *p); // 5 … p の指す先の値2行目と3行目が同じ番地になるのがポイントです。ここが一致していれば、p は正しく a を指しています。
まとめ
& は住所を調べる、* はその住所を訪ねる。*&a は a に戻ります。p と *p は別物で、前者は番地、後者は指している先の値です。この区別だけは、声に出して確かめる癖をつけてください。