C言語入門|scanfの使い方と注意点

ここまでで、文字列を
表示する・コピーする・連結する・生成する
といった処理を学んできましたね。

次はその逆、
キーボードから情報を受け取る
場面を扱います。

その代表が scanf 関数です。

ただし scanf は、
「便利だけど、油断すると一瞬で事故る」
という、かなりクセの強い関数でもあります。

この節では、
scanf が何をしている関数なのか
なぜ注意が必要なのか
を、メモリ視点でしっかり整理していきましょう。

scanfは「入力を書き込む」関数

scanf は、printf とよく似た見た目をしていますが、
中身はまったく逆の動きをします。

関数役割
printf値を読み取り、画面に出力する。
scanf入力を読み取り、メモリに書き込む。

つまり scanf は、
値そのものではなく、書き込み先の場所
を教えてあげなければなりません。

ここが、初心者がつまずきやすい最大のポイントです。

scanfの書式

int scanf(const char* format, ...);
引数意味
format入力用の書式文字列
...入力された値を書き込む先のアドレス
戻り値正常に読み取れた項目数(失敗時はEOF)

scanf は、
書式文字列を見ながら、
キーボード入力を解析し、
指定されたメモリ領域へ書き込みます。

なぜ scanf では & が必要なのか

次のコードを見てみましょう。

サンプルプログラム

プロジェクト名:11-11-1 ソースファイル名: sample11-11-1.c

#include <stdio.h>

int main(void)
{
    char player[64];
    int score;

    printf("プレイヤー名とスコアを入力してください。\n");
    scanf("%s %d", player, &score);

    printf("名前:%s\nスコア:%d\n", player, score);
    return 0;
}

ここで不思議に思うのが、
score だけ & が付いている点です。

printf と scanf の決定的な違い

この違いは、
後続引数に期待している型
が異なるために起こります。

書式printf が期待する型scanf が期待する型
%dintint*
%fdoublefloat*
%schar*char*

printf は
「表示したい値そのもの」
を受け取ります。

一方 scanf は
「ここに書き込んでください」
という アドレス を受け取ります。

そのため、

  • int 型 → &変数名
  • char 配列 → 配列名(先頭アドレス)

という指定が必要になるのです。

%s と 配列名が特別扱いな理由

scanf("%s", player);

ここで &player を付けていないのは、
配列名が 特殊構文により先頭アドレスに変換される
からです。

書き方実際に渡されているもの
player&player[0]
&scorescore のアドレス

つまり、
%s でもアドレスはちゃんと渡されています。

scanfで最も危険なのは文字列入力

scanf の中で、
最も事故率が高いのが %s です。

理由は単純で、
入力文字数の上限を自動で制限しない
からです。

char name[16];
scanf("%s", name);

この場合、

  • 15文字以内なら安全
  • 16文字以上入力されたら即オーバーラン

となります。

scanf は、
「入るだけ入れる」
という動作を平然と行います。

scanf と「3つの領域」の関係

scanf を安全に使うには、
文字列の「3つの領域」を常に意識する必要があります。

領域意味
使用中入力された文字列 + \0
使用可能配列や malloc で確保した範囲
使用禁止触れてはいけない領域

scanf は、
使用可能領域のサイズを一切見ません。

そのため、
使用可能領域を超えない保証を、呼び出し側がする必要があります。

scanfを使うときの心構え

scanf を使う場面では、
次のことを必ず意識しましょう。

ポイント内容
書き込み先は必ずアドレス値ではなく場所を渡す。
配列サイズを把握する%s は特に注意
戻り値を確認する入力失敗を見逃さない。

scanf は、
仕様を正しく理解して使えば便利ですが、
「なんとなく使う」と危険な関数です。