閉じる

【C言語】scanf関数の使い方入門|数値・文字列入力とエラー対策

C言語でキーボードから値を入力するとき、最初に出会うのがscanf関数です。

とても便利な反面、使い方を間違えるとバッファにゴミが残ったり、プログラムが暴走したりする原因にもなります。

本記事では、整数・浮動小数点・文字・文字列の入力から、エラー対策や安全な入力パターンまで、scanfの基本と実践的な使い方を体系的に解説します。

scanf関数とは

scanf関数の基本構文と書き方

C言語のscanf関数は、標準入力(stdin)から型に応じて値を読み取るための関数です。

基本的な書き方は次のとおりです。

C言語
#include <stdio.h>

int main(void) {
    int x;
    // %d : 整数として読み取るフォーマット指定子
    // &x : 変数xの「アドレス」を渡すことが重要
    scanf("%d", &x);
    printf("x = %d\n", x);
    return 0;
}

最も重要なポイントは、変数そのものではなく&変数名を渡すことです。

scanfは、渡されたアドレスが指すメモリ領域に値を書き込むため、アドレス演算子&を付け忘れると、未定義動作になり大変危険です。

また、フォーマット文字列では、読み取るデータの種類をフォーマット指定子で指示します。

例えば、%dはint、%fはfloatといった具合です。

この指定子と変数の型が一致していることが非常に重要です。

標準入力(stdin)とバッファの仕組み

C言語の標準入力は、OSが提供する入力バッファを通じて動作します。

ユーザがキーボードで入力し、Enterキーを押すと、その行の文字列が一旦バッファに溜められ、scanfはそこから必要な分だけ読み取ります。

例えば、ユーザが次のように入力したとします。

  • 入力: 123 45⏎

このとき、バッファには"123 45\n"という文字列が格納されます。

scanf("%d", &a);は先頭から数字部分123だけを読み取り、後ろの" 45\n"はバッファに残ります。

この「バッファに残る」という挙動が、複数回scanfを呼び出したときの思わぬ不具合の原因になります。

後ほど「入力バッファをクリアする方法」の項目で詳しく扱います。

数値入力のscanfの使い方

int型・long型を読み取る%dと%ld

整数を読み取るときは、変数の型に応じたフォーマット指定子を使用します。

代表的なものは次のとおりです。

フォーマット指定子
int%dscanf(“%d”, &x);
long%ldscanf(“%ld”, &lx);
long long%lldscanf(“%lld”, &llx);

実際のサンプルコードを見てみます。

C言語
#include <stdio.h>

int main(void) {
    int a;
    long b;

    printf("int型の値を入力してください: ");
    scanf("%d", &a);   // int用のフォーマット指定子%d

    printf("long型の値を入力してください: ");
    scanf("%ld", &b);  // long用のフォーマット指定子%ld

    printf("入力されたint: %d\n", a);
    printf("入力されたlong: %ld\n", b);

    return 0;
}

このように、フォーマット指定子と変数の型を正しく対応させることが、安全な入力処理の第一歩です。

float・doubleを読み取る%fと%lf

浮動小数点数を入力する場合、floatとdoubleで使用するフォーマット指定子が少し異なります。

フォーマット指定子
float%fscanf(“%f”, &f);
double%lfscanf(“%lf”, &d);

サンプルコードで確認します。

C言語
#include <stdio.h>

int main(void) {
    float f;
    double d;

    printf("float型の値を入力してください: ");
    scanf("%f", &f);    // float用 %f

    printf("double型の値を入力してください: ");
    scanf("%lf", &d);   // double用 %lf (lが付く)

    printf("入力されたfloat: %f\n", f);
    printf("入力されたdouble: %lf\n", d);

    return 0;
}

実行例(入力: 1.23⏎ 4.5678⏎)は次のようになります。

float型の値を入力してください: 1.23
double型の値を入力してください: 4.5678
入力されたfloat: 1.230000
入力されたdouble: 4.567800

ここでdoubleに%fを使わないことが重要です。

フォーマット指定子と型が合わないと、内部表現のサイズが異なるため、メモリ破壊につながる可能性があります。

複数の数値を一度に入力する方法

scanfは、1回の呼び出しで複数の値を読み取ることができます。

ユーザにとっても「1行で入力できる」ため便利です。

C言語
#include <stdio.h>

int main(void) {
    int a, b, c;

    printf("3つの整数をスペース区切りで入力してください: ");
    // %d %d %d の3つ分を一度に読み取る
    scanf("%d %d %d", &a, &b, &c);

    printf("a = %d, b = %d, c = %d\n", a, b, c);

    return 0;
}

入力例として10 20 30⏎と打ち込むと、次のように表示されます。

3つの整数をスペース区切りで入力してください: 10 20 30
a = 10, b = 20, c = 30

このとき、スペースや改行などの空白文字は区切りとして扱われます。

そのため、次のように改行を挟んでも問題なく読み取れます。

  • 入力例
    10⏎
    20⏎
    30⏎

数値入力でありがちなエラーと対処法

数値入力でよく起きる問題とその対策を整理します。

問題のパターン原因対策のポイント
ユーザが数字以外(abcなど)を入力数値として解釈できず、scanfが失敗scanfの戻り値をチェックし、エラー処理を行う
入力バッファに改行や余分な文字が残り、次のscanfが誤動作バッファに未消費文字が残ったまま次の入力へバッファを読み捨てる・fgetsと組み合わせて使う
%dと%ldなど、型とフォーマット指定子の不一致メモリサイズの違いによるメモリ破壊、未定義動作型に合ったフォーマット指定子を使う

最も重要なのは、scanfの戻り値を必ず確認することです。

戻り値の詳細は後半で解説しますが、読み取りに成功した項目数が返ってきます。

成功したかどうかを確認し、失敗した場合はエラーメッセージを表示して再入力を促すような処理を行うと、安全性が高まります。

文字・文字列入力のscanfの使い方

1文字入力の%cと空白文字の扱い

1文字だけを読み取りたいときは%cを使います。

C言語
#include <stdio.h>

int main(void) {
    char c;

    printf("1文字入力してください: ");
    scanf("%c", &c);  // 1文字だけをそのまま読み取る

    printf("入力された文字: %c\n", c);

    return 0;
}

ここで注意が必要なのは、%cは空白文字も含めて1文字をそのまま読み取るという点です。

先に数値などをscanfで読み込んだあと、続けて%cを使うと、残っている改行文字'\n'を読んでしまうことがあります。

その対策として、フォーマット指定子の前に空白を入れる方法があります。

C言語
scanf(" %c", &c);  // 先頭のスペースにより、空白文字を読み飛ばしてから1文字読む

この" %c"の先頭スペースは「空白類をすべてスキップする」という意味になり、余分な改行やスペースを読み飛ばしてから1文字を取得してくれます。

文字列入力の%sとバッファオーバーフロー対策

文字列を入力するときには%sを用います。

ただし、%sは非常に危険になりやすいため、必ず読み取る最大文字数を指定してください。

C言語
#include <stdio.h>

int main(void) {
    char name[16];  // 最大15文字 + 終端文字'\0'

    printf("名前を入力してください: ");
    // %15s として、最大15文字までしか読み取らないようにする
    scanf("%15s", name);  // &は不要(配列名は先頭アドレス)

    printf("こんにちは、%sさん!\n", name);

    return 0;
}

ここでのポイントは次のとおりです。

  • 配列char name[16]には最大15文字+終端文字1つを格納できます。
  • %15sとすることで、最大15文字までに制限し、バッファオーバーフローを防ぎます。
  • %sは空白文字(スペース・タブ・改行)で入力を区切るため、「山田 太郎」のような空白を含む名前は途中までしか読み取れません。

なお、配列に対しては&nameではなくnameを渡します。

配列名は先頭要素のアドレスとして扱われるためです。

空白を含む文字列入力とscanfの限界

%cst-code>%sは、空白文字を区切りとして扱うため、空白を含む文字列を一度に読み込むことはできません

例えば、次のようなコードを考えます。

C言語
#include <stdio.h>

int main(void) {
    char str[64];

    printf("好きなフレーズを入力してください: ");
    scanf("%63s", str);

    printf("入力された文字列: %s\n", str);

    return 0;
}

ここで、ユーザがHello world⏎と入力すると、scanfは"Hello"だけを読み取り、" world\n"はバッファに残ってしまいます。

そのため、空白を含む1行全体を読み取りたい場合、scanfだけでは不十分です。

このような用途にはfgetsを使うのが一般的です。

次の節で詳しく扱います。

getsとの違いとfgetsの併用方法

C言語にはかつてgetsという関数が存在しましたが、非常に危険であることが明らかになり、C11規格で標準ライブラリから完全に削除されています。

getsが危険な理由は入力長の制限ができないからです。

ユーザが長い文字列を入力すると、簡単にバッファオーバーフローが発生し、セキュリティホールになります。

代わりにfgetsを使用します。

fgetsは最大文字数を指定できるため、安全に1行を読み取ることができます。

C言語
#include <stdio.h>

int main(void) {
    char line[64];

    printf("1行の文章を入力してください: ");
    // sizeof(line)で、配列のサイズ(バイト数)をそのまま指定
    // fgetsは最大63文字+終端'\0'を読み取り、必要なら末尾に'\n'も含める
    if (fgets(line, sizeof(line), stdin) != NULL) {
        printf("入力された行:\n%s", line);
    } else {
        printf("入力に失敗しました。\n");
    }

    return 0;
}

実際の入力処理ではfgetsで1行読み取り、その文字列をsscanfstrtolなどで解析する二段構えの方法が、安全で柔軟なパターンとしてよく使われます。

この点は後半の「安全な入力処理のためのコーディングパターン」で具体的に解説します。

scanfのエラー対策と実践テクニック

scanfの戻り値で入力エラーを検出する

scanfは読み取れた項目数を戻り値として返します。

これを利用して、入力の成否を確実に判定できます。

C言語
#include <stdio.h>

int main(void) {
    int a, b;
    int ret;

    printf("2つの整数を入力してください: ");

    // 読み取れた数をretに受け取る
    ret = scanf("%d %d", &a, &b);

    if (ret == 2) {
        // 2つとも正常に読み取れた
        printf("a = %d, b = %d\n", a, b);
    } else if (ret == EOF) {
        // 入力ストリームの終端(EOF)に達した場合
        printf("入力が途中で終了しました(EOF)。\n");
    } else {
        // 0または1など、期待した数だけ読み取れなかった
        printf("入力エラー: 整数2つを正しく入力してください。\n");
    }

    return 0;
}

このように、戻り値を使って入力エラーを検出し、エラー時には再入力を促すようなループを組めば、ユーザが誤入力しても安全に対処できます。

入力バッファをクリアする安全な方法

scanfで読み取りに失敗した場合や、想定より短い入力しか行われなかった場合、入力バッファに余計な文字が残ることがあります。

そのまま次のscanfを実行すると、残りの文字がそのまま次の入力として扱われ、意図しない挙動を招きます。

もっとも一般的でポータブルな「バッファを捨てる」方法は、getcharで1文字ずつ読み取り、改行までを捨てるやり方です。

C言語
#include <stdio.h>

// 改行が見つかるかEOFになるまで、標準入力を読み捨てる関数
void clear_input_buffer(void) {
    int ch;
    // '\n'が出るまで読み捨てる
    while ((ch = getchar()) != '\n' && ch != EOF) {
        ; // 何もしない(捨てるだけ)
    }
}

int main(void) {
    int x;
    int ret;

    while (1) {
        printf("整数を入力してください: ");

        ret = scanf("%d", &x);

        if (ret == 1) {
            // 正常に1件読み取れたのでループを抜ける
            break;
        } else {
            printf("整数として解釈できませんでした。もう一度入力してください。\n");
            clear_input_buffer();  // 誤入力の残りを捨てる
        }
    }

    printf("入力された整数: %d\n", x);

    return 0;
}

このように専用のクリア関数を用意し、誤入力時や次の入力の前に必ず呼び出すことで、入力周りの不具合を大幅に減らせます。

フォーマット指定子と変数型の不一致エラー

scanfのバグで特に危険なのが、フォーマット指定子と変数型の不一致です。

例えば、int型の変数に対して%ldを使うなどのミスは、コンパイル時に警告が出ない場合もあり、実行時にメモリ破壊を引き起こすことがあります。

具体例を挙げます。

C言語
#include <stdio.h>

int main(void) {
    int x;
    long y;

    // 誤った例(コメントのとおり、絶対にやってはいけない)
    // scanf("%ld", &x);  // xはintなのに%ld(long)を指定 → 未定義動作の可能性

    // 正しい例
    printf("int型を入力: ");
    scanf("%d", &x);   // intには%d

    printf("long型を入力: ");
    scanf("%ld", &y);  // longには%ld

    printf("x = %d, y = %ld\n", x, y);

    return 0;
}

フォーマット指定子と型の対応は、早めに一覧で覚えておくと安全です。

フォーマット指定子(入力: scanf)
int%d
unsigned%u
long%ld
long long%lld
float%f
double%lf
char%c
文字列配列%s (最大長指定推奨)

コンパイラの警告オプション(-Wall -Wextraなど)を有効にしておくと、不一致の多くは警告で検出できます。

警告は必ず確認し、可能な限り0件にすることが品質向上につながります。

安全な入力処理のためのコーディングパターン

最後に、実務でも使いやすい安全な入力パターンを紹介します。

ポイントは「1行として安全に読み取る処理」と「値として解釈する処理」を分けることです。

パターン1: fgets + sscanfで数値を読む

C言語
#include <stdio.h>

int main(void) {
    char line[64];
    int x;

    while (1) {
        printf("整数を入力してください: ");

        // 1行分を安全に読み取る
        if (fgets(line, sizeof(line), stdin) == NULL) {
            printf("入力ストリームが終了しました(EOF)。\n");
            return 1;  // 異常終了
        }

        // 読み取った行から整数1つを取り出す
        if (sscanf(line, "%d", &x) == 1) {
            // 正常に1件読み取れたらループを抜ける
            break;
        } else {
            printf("整数として解釈できません。もう一度入力してください。\n");
        }
    }

    printf("入力された整数: %d\n", x);

    return 0;
}

この方法では、標準入力バッファに文字が残る心配がなく、ユーザが余計な文字を入力してもその行の中だけで完結して処理できます。

パターン2: fgets + strtolで整数を検証付きで読む

より厳密に整数を扱いたい場合は、strtolなどの関数で文字列から数値を変換します。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int main(void) {
    char line[64];
    long value;
    char *endptr;

    while (1) {
        printf("整数(long)を入力してください: ");

        if (fgets(line, sizeof(line), stdin) == NULL) {
            printf("入力ストリームが終了しました(EOF)。\n");
            return 1;
        }

        errno = 0;
        value = strtol(line, &endptr, 10);  // 10進数として解釈

        // 変換できた部分が1文字もない(=数字がない)場合
        if (endptr == line) {
            printf("数字が含まれていません。もう一度入力してください。\n");
            continue;
        }

        // オーバーフロー・アンダーフローの検出
        if ((value == LONG_MAX || value == LONG_MIN) && errno == ERANGE) {
            printf("値が大きすぎるか小さすぎます。範囲内で入力してください。\n");
            continue;
        }

        // 行末までに余計な文字がないかをチェック(改行か終端であればOK)
        while (*endptr == ' ' || *endptr == '\t') {
            endptr++;
        }

        if (*endptr != '\n' && *endptr != '\0') {
            printf("数字の後ろに余計な文字があります。もう一度入力してください。\n");
            continue;
        }

        // ここまで来れば安全にlongとして扱える
        break;
    }

    printf("入力された値: %ld\n", value);

    return 0;
}

このような2段階の入力パターンは、単純なscanfよりも記述量は増えますが、その分だけ安全で堅牢なプログラムになります。

ユーザインタフェースや外部入力を扱うプログラムでは、特にこのパターンが有効です。

まとめ

scanfはC言語における基本的な入力手段ですが、フォーマット指定子と型の対応入力バッファの挙動戻り値のチェックなどを理解していないと、思わぬ不具合やセキュリティ問題を引き起こします。

本記事では、整数・浮動小数点・文字・文字列の扱いから、バッファクリアやfgetsとの併用、さらにはfgets+解析関数による安全な入力パターンまで解説しました。

日常的な小さなプログラムではscanfを、外部からの信頼できない入力を扱う場面ではfgets+解析という形で使い分け、堅牢で読みやすいコードを心がけてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!