scanfの&の正体
おまじないを回収します
第2章の「scanfで入力」で、こう書きました。
int score;
scanf("%d", &score);このとき & については「scanf に変数を渡すときは前に付けると覚えてください、理由は第7章で分かります」と書いて、説明を先送りにしました。あわせて「なぜ printf には要らないのに scanf には要るのか」も宿題にしていました。ここまでで材料がそろったので、両方まとめて回収します。
値を渡すだけでは書き換えられない
第5章で、関数の引数は 値のコピー で渡ると学びました。呼び出された側は写しをもらうだけなので、その写しをいくら書き換えても、呼び出した側の変数は元のままです。
scanf も普通の関数です。もし scanf("%d", score); と書いたら、scanf に渡るのは score の中身の写しだけです。scanf はキーボードから読んだ数値を「どこに」置けばよいのか分かりません。渡された写しに書いても、呼び出し元の score には届きません。
そこで &score と書いて、値ではなく 番地 を渡していたのです。番地さえ受け取れば、scanf は前回学んだ * の要領で、その場所へ直接書き込めます。scanf の中では、おおよそ次のようなことが起きています。
/* scanf の内部のイメージ */
int *p = 受け取った番地;
*p = キーボードから読んだ値;つまり第2章の & は、scanf に「ここへ書き込んでください」と場所を教えるためのものでした。
文字列に & を付けない理由
第6章で名前を読むとき、& を付けませんでした。
char name[32];
scanf("%31s", name);これは例外ではありません。配列名を式の中に書くと、先頭要素の番地に読み替えられます。つまり name はすでに番地なので、さらに & を付ける必要がないのです。逆に int や double のような普通の変数は番地ではないので、& が要ります。
printf に & が要らない理由
同じことを printf 側から見ると、区別がはっきりします。
printf("%d\n", score); // & は付けない
scanf("%d", &score); // & を付けるprintf は値を表示するだけで、score を書き換えません。読むだけなら写しをもらえば足りるので、番地は不要です。一方 scanf は書き込むので番地が要ります。読むだけなら値、書き換えてもらうなら番地 という区別が、この章を通した考え方の軸になります。
付け忘れは最悪の事故
& を忘れても、コンパイラは警告を出すだけで止まらないことがあります。実行すると、でたらめな番地に書き込むことになり、その場では動いているように見えて、あとから関係のない変数が壊れることさえあります。原因が見えにくい種類のバグなので、scanf を書いたら & の有無を必ず見直してください。
では、scanf が本当に「教えた番地」へ書いていることを、ポインタで確かめてみましょう。
要件
- 1行目で名前を、2行目で点数を読み込む
- 点数を読む前に int *p に score の番地を入れておく
- 名前を 「名前は佐藤です」 の形で表示する
- scanf のあとに *p で読んだ値を 「scanfが書いた値をpで読むと78点です」 の形で表示する
- p と &score が同じ番地かを判定して 「pとscanfに渡した番地は同じです」 と表示する
入出力例
main("佐藤", "78") → "名前は佐藤です
scanfが書いた値をpで読むと78点です
pとscanfに渡した番地は同じです"
main("田中", "100") → "名前は田中です
scanfが書いた値をpで読むと100点です
pとscanfに渡した番地は同じです"