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 はすでに番地なので、さらに & を付ける必要がないのです。逆に intdouble のような普通の変数は番地ではないので、& が要ります。

printf に & が要らない理由

同じことを printf 側から見ると、区別がはっきりします。

printf("%d\n", score); // & は付けない scanf("%d", &score); // & を付ける

printf は値を表示するだけで、score を書き換えません。読むだけなら写しをもらえば足りるので、番地は不要です。一方 scanf は書き込むので番地が要ります。読むだけなら値、書き換えてもらうなら番地 という区別が、この章を通した考え方の軸になります。

付け忘れは最悪の事故

& を忘れても、コンパイラは警告を出すだけで止まらないことがあります。実行すると、でたらめな番地に書き込むことになり、その場では動いているように見えて、あとから関係のない変数が壊れることさえあります。原因が見えにくい種類のバグなので、scanf を書いたら & の有無を必ず見直してください。

では、scanf が本当に「教えた番地」へ書いていることを、ポインタで確かめてみましょう。

要件

  1. 1行目で名前を、2行目で点数を読み込む
  2. 点数を読む前に int *p に score の番地を入れておく
  3. 名前を 「名前は佐藤です」 の形で表示する
  4. scanf のあとに *p で読んだ値を 「scanfが書いた値をpで読むと78点です」 の形で表示する
  5. p と &score が同じ番地かを判定して 「pとscanfに渡した番地は同じです」 と表示する

入出力例

main("佐藤", "78") → "名前は佐藤です scanfが書いた値をpで読むと78点です pとscanfに渡した番地は同じです" main("田中", "100") → "名前は田中です scanfが書いた値をpで読むと100点です pとscanfに渡した番地は同じです"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

scanfの&の正体

⌘S で保存