閉じる

【C言語】標準入出力を解説|stdin・stdout・stderr総まとめ

C言語のプログラムは、キーボードからの入力や画面への表示を、とても頻繁に行います。

このとき直接キーボードや画面を触るのではなく、標準入力(stdin)・標準出力(stdout)・標準エラー(stderr)という「通り道」を使うのがポイントです。

本記事では、これら3つの標準入出力の仕組みと使い方、リダイレクトやバッファリングの実用的な知識まで、図解とサンプルコードを交えながら詳しく解説します。

C言語の標準入出力とは

標準入力(stdin)・標準出力(stdout)・標準エラー(stderr)の基本

C言語では、プログラムが外部とやりとりするために、次の3つの標準的なストリームが最初から用意されています。

  • 標準入力(stdin)
    通常はキーボードからの入力が流れてくる通り道です。
  • 標準出力(stdout)
    通常は画面(ターミナル)に向かって文字や結果を出す通り道です。
  • 標準エラー(stderr)
    エラーメッセージや警告を、標準出力とは別に出すための通り道です。

どれもstdio.hで宣言されているFILE *型のポインタで、次のように定義されています。

C言語
#include <stdio.h>

int main(void) {
    // stdin, stdout, stderr は FILE * 型の外部変数として宣言されている
    if (stdin  == NULL) { /* まず起こらないが、理屈上のチェック */ }
    if (stdout == NULL) { /* ・・・ */ }
    if (stderr == NULL) { /* ・・・ */ }

    return 0;
}

重要なのは、これら3つは「最初から開かれているファイルストリーム」だという点です。

自分でfopenする必要はなく、すぐにprintfscanfなどを使って入出力できます。

標準入出力と端末・ファイルの関係

標準入出力は、普段はターミナル(端末)とつながっていますが、OSの機能によってファイルやパイプなど、別の入出力先に差し替えることができます。

これがシェルで行うリダイレクトです。

例えば、LinuxやmacOSのシェルで次のように実行するとします。

Shell
./a.out < input.txt > output.txt 2> error.log

このとき、

  • stdininput.txt から読み込まれる
  • stdoutoutput.txt に書き出される
  • stderrerror.log に書き出される

という関係になります。

プログラムの中身は変えていないのに、入出力の行き先だけが変わるため、同じプログラムを対話的にもバッチ処理でも再利用できるというメリットがあります。

C言語の入出力とバッファリングの概要

標準入出力では、バッファリングという仕組みが使われます。

これは、入出力を1文字ずつOSに依頼するのではなく、一定量たまったらまとめて処理することで、性能を上げる仕組みです。

おおまかな性質は次の通りです。

  • stdin
    OS側で行単位やブロック単位のバッファリングが行われることが多く、ユーザーがEnterを押すまでプログラムに届かない場合があります。
  • stdout
    端末に接続されているときは行バッファリング(改行でフラッシュ)、ファイルやパイプに接続されているときは全バッファリング(一定サイズたまるまで書き出さない)のことが多いです。
  • stderr
    通常はバッファリングされないか、行バッファリングされるため、エラーがすぐに画面に表示されます。

このようなバッファリングによって、プログラムの性能と利便性が大きく変わります。

後半のセクションで、改行とフラッシュの関係も含めて詳しく見ていきます。

stdin(標準入力)の使い方

stdinとキーボード入力の仕組み

標準入力(stdin)は、通常ターミナルのキーボードと接続されており、ユーザーがタイプした文字をプログラムに届けます。

ただし、プログラムはユーザーがEnterを押すまで待つことが多く、1文字打つごとに即座に読めるわけではない点に注意が必要です。

OSやターミナルが、入力行を一時的にバッファにためて、Enterが押されたタイミングでプログラムのstdinに渡すイメージです。

この仕組みを前提にして、scanffgets などの関数が動作しています。

scanfとfscanfでstdinから読み取る

scanf は、標準入力からフォーマットに従って値を読み取るための関数です。

fscanf は、任意のFILE *から読む汎用版ですが、stdinを指定すればscanfと同等の動作になります。

scanfの基本的な使い方

C言語
#include <stdio.h>

int main(void) {
    int age;
    double height;

    printf("年齢と身長を入力してください (例: 25 170.5)\n");
    // scanf は標準入力(stdin)から読み取る
    // "%d %lf" は「整数」と「倍精度浮動小数点数」の書式指定子です
    if (scanf("%d %lf", &age, &height) == 2) {
        printf("あなたの年齢は %d 歳、身長は %.1f cm ですね。\n", age, height);
    } else {
        printf("入力形式が正しくありません。\n");
    }

    return 0;
}

標準入力から読み取る場合、入力の成功数(読み取れた項目数)を戻り値で確認することが大切です。

戻り値をチェックせずに使うと、意図しない未初期化の値を使ってしまう危険があります。

fscanfで明示的にstdinを使う

C言語
#include <stdio.h>

int main(void) {
    int x, y;

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

    // fscanf に stdin を渡すことで、明示的に標準入力から読み取ります
    if (fscanf(stdin, "%d %d", &x, &y) == 2) {
        printf("合計は %d です。\n", x + y);
    } else {
        fprintf(stderr, "入力エラーが発生しました。\n");
    }

    return 0;
}
実行結果
2つの整数を入力してください (例: 10 20)
10 20
合計は 30 です。

scanffscanf(stdin, ...)は事実上同じ動作ですが、関数ポインタや抽象化を行うときに「どのストリームから読むか」を引数で切り替えたい場合は、fscanfの形を意識しておくと便利です。

getchar・fgetsで1文字・1行を読み取る

フォーマット指定で数値を読むだけでなく、文字や行単位で文字列として読みたいこともよくあります。

その場合はgetcharfgetsを使います。

getcharで1文字ずつ読む

C言語
#include <stdio.h>

int main(void) {
    int ch;

    printf("1行入力してください (EOF で終了)。\n");

    // getchar は stdin から 1文字ずつ読み込みます
    // 戻り値は int 型で、EOF を判定できるようになっています
    while ((ch = getchar()) != EOF) {
        // 読み取った文字をそのまま標準出力に書き出します
        putchar(ch);

        // 改行が来たら一度ループを抜けます
        if (ch == '\n') {
            break;
        }
    }

    return 0;
}
実行結果
1行入力してください (EOF で終了)。
Hello, C language!
Hello, C language!

EOFは入力の終わりを示す特別な値で、LinuxやmacOSではCtrl + D、WindowsではCtrl + Zを押して送ることが多いです。

fgetsで1行分の文字列を読む

fgetsは、1行をバッファに読み込む関数です。

バッファサイズを指定することで、バッファオーバーフローを防ぎながら安全に読み取りができます。

C言語
#include <stdio.h>

#define BUF_SIZE 64

int main(void) {
    char buf[BUF_SIZE];

    printf("名前を入力してください。\n");
    // fgets は「最大 BUF_SIZE - 1 文字」まで読み込み、
    // 終端に必ず '\0' を付けてくれます
    if (fgets(buf, BUF_SIZE, stdin) != NULL) {
        printf("こんにちは、%sさん。\n", buf);
    } else {
        fprintf(stderr, "入力に失敗しました。\n");
    }

    return 0;
}
実行結果
名前を入力してください。
Yamada
こんにちは、Yamada
さん。

この例では改行文字'\n'も文字列に含まれるため、出力が少し見づらくなっています。

改行を削除したい場合は、末尾が'\n'なら'\0'に置き換えるなどの処理を行います。

リダイレクトでstdinにファイルを渡す方法

標準入力は、シェルの<リダイレクトによって、キーボードではなくファイルから読み込ませることができます。

Shell
./a.out < data.txt

プログラム側では特別な処理は不要で、普段通りscanffgetsを使えば、そのままdata.txtの内容を入力として利用できます。

対話的なプログラムを、そのままバッチ処理用に使えるのでとても便利です。

stdout(標準出力)の使い方

stdoutの役割と画面出力の仕組み

標準出力(stdout)は、プログラムの通常の結果やメッセージを送るためのストリームです。

多くの場合はターミナルの画面と接続されていますが、リダイレクトでファイルや別のプログラムへつなぎ替えられることが大きな特徴です。

printfputsputcharなどの関数は、特に指定がなければstdoutに出力します。

これらは直接画面に書いているのではなく、まずはバッファに書き込み、それがタイミングよくフラッシュされて画面やファイルに届くという仕組みで動いています。

printf・fprintfを使った標準出力

printfは標準出力にフォーマット付きで文字列を出す関数です。

fprintfは、任意のFILE *に対して同様の出力を行う関数で、stdoutを指定すればprintfと同じになります。

C言語
#include <stdio.h>

int main(void) {
    int a = 10;
    double b = 3.14159;

    // printf は標準出力(stdout)に出力します
    printf("a = %d, b = %.2f\n", a, b);

    // fprintf に stdout を渡すことで、明示的に標準出力を指定できます
    fprintf(stdout, "この行も stdout に出力されます。\n");

    return 0;
}
実行結果
a = 10, b = 3.14
この行も stdout に出力されます。

同じ関数インターフェースで、出力先だけ変えられる点がfprintfの強みです。

ログファイルやメモリ上のストリームに出力する際にも便利です。

puts・putcharでシンプルに出力する

簡単に文字列や1文字だけを出したい場合は、putsputcharを使うとコードがすっきりします。

C言語
#include <stdio.h>

int main(void) {
    // puts は標準出力に文字列を出力し、最後に自動的に改行を付けます
    puts("Hello, world!");
    puts("C言語の標準出力のサンプルです。");

    // putchar は 1文字だけ出力します
    putchar('A');
    putchar('\n');

    return 0;
}
実行結果
Hello, world!
C言語の標準出力のサンプルです。
A

printfほど柔軟ではありませんが、フォーマット指定が不要なときには高速でシンプルです。

改行とバッファフラッシュの関係

標準出力は、端末につながっている場合、1行ごとにフラッシュされる「行バッファリング」になることが多いです。

つまり、

  • 改行'\n'が出力された
  • バッファが一杯になった
  • 明示的にfflush(stdout)を呼んだ
  • プログラムが正常終了した

といったタイミングで、バッファの中身が画面やファイルに書き出されます。

次の例では、'\n'を書かない場合に表示が遅れる様子を観察できます。

C言語
#include <stdio.h>

#ifdef _WIN32
#include <Windows.h>
#define SLEEP(ms) Sleep(ms)
#else
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#endif

int main(void) {
    printf("カウントダウン開始: ");

    for (int i = 3; i >= 1; --i) {
        printf("%d ", i);  // 改行なしで出力
        fflush(stdout);    // バッファを明示的にフラッシュ
        SLEEP(1000);       // 1秒待つ
    }

    printf("スタート!\n"); // ここで改行を含めて出力

    return 0;
}
実行結果
カウントダウン開始: 3 2 1 スタート!

もしfflush(stdout)を呼ばなければ、環境によってはカウントダウンの数字がまとめて表示されてしまうことがあります。

リアルタイム性が必要な場合や、プログレスバーなどを表示するときには、明示的なフラッシュが重要です。

リダイレクトでstdoutをファイルに書き出す

標準出力は、シェルの>演算子で簡単にファイルへリダイレクトできます。

Shell
./a.out > result.txt

この場合、printfputsで出力した内容は、画面には表示されずすべてresult.txtに書き込まれることになります。

既存のファイルは上書きされる点に注意してください。

追記したい場合は>>を使います。

Shell
./a.out >> result.log

このようにプログラム側のコードを変えずに、出力先だけを変えられるのが標準出力とリダイレクトの大きな利点です。

stderr(標準エラー出力)の使い方

stderrの特徴とstdoutとの違い

stderr(標準エラー出力)は、エラーや警告メッセージを出すためのストリームです。

見た目はstdoutと同じように画面に表示されますが、次のような違いがあります。

  • 通常、バッファリングされにくい(多くの処理系では非バッファまたは行バッファ)
  • stdoutとは別にリダイレクトできるため、エラーログだけファイルに分けられる

この性質により、通常の出力とエラーメッセージをきれいに分離できるため、大規模なシステムやパイプライン処理で非常に重要な役割を果たします。

エラーメッセージをfprintfでstderrに出力

エラーメッセージはfprintfを使ってstderrに送るのが一般的です。

C言語
#include <stdio.h>

int main(void) {
    int n;

    printf("0でない整数を入力してください。\n");

    if (scanf("%d", &n) != 1) {
        // 入力形式が不正な場合
        fprintf(stderr, "入力エラー: 整数を読み取れませんでした。\n");
        return 1; // 異常終了コード
    }

    if (n == 0) {
        // 意図しない値が入力された場合
        fprintf(stderr, "入力エラー: 0 は無効な値です。\n");
        return 1;
    }

    printf("1/%d = %f\n", n, 1.0 / n);
    return 0;
}
実行結果
0でない整数を入力してください。
0
入力エラー: 0 は無効な値です。

このように、ユーザー向けの通常結果はstdout問題を知らせるメッセージはstderrと分けておくと、後からログ解析するときにも便利です。

バッファリングされないstderrのメリット

stderrは、標準出力と異なりノーバッファあるいは行バッファで動作することが多いため、プログラムがクラッシュした場合でも、エラーメッセージが残りやすいというメリットがあります。

例えば、次のように途中で異常終了するプログラムを想像してみます。

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

int main(void) {
    printf("このメッセージは stdout に出力されますが、");
    fprintf(stderr, "エラー発生直前のメッセージ(stderr)。\n");

    // ここで異常終了させる
    abort();

    // 通常ここには到達しません
    printf("このメッセージは表示されません。\n");

    return 0;
}

このような状況では、stderrのメッセージはすでに出力されている一方、stdoutのメッセージはバッファに残ったまま消えてしまう可能性があります。

そのため、致命的なエラーやデバッグ情報はstderrに書くのがよくある流儀です。

リダイレクト時のstderrとログ出力の実践例

シェルでは、標準出力と標準エラー出力を別々にリダイレクトできます。

ファイルディスクリプタ番号1がstdout、2がstderrに対応しています。

Shell
# 標準出力を result.txt に、標準エラーを error.log に分ける
./a.out > result.txt 2> error.log

プログラム側では、次のようにstdoutとstderrを使い分けておくだけで、ログの整理が簡単になります。

C言語
#include <stdio.h>

int main(void) {
    for (int i = 0; i < 5; ++i) {
        printf("処理ステップ %d 完了\n", i);  // 通常の進行ログ (stdout)

        if (i == 2) {
            // 警告メッセージを stderr に出力
            fprintf(stderr, "警告: ステップ %d で軽微な問題が発生しました。\n", i);
        }
    }

    return 0;
}
実行結果
処理ステップ 0 完了
処理ステップ 1 完了
処理ステップ 2 完了
処理ステップ 3 完了
処理ステップ 4 完了
警告: ステップ 2 で軽微な問題が発生しました。

ターミナルで実行すると両方とも画面に混ざって見えますが、

Shell
./a.out > normal.log 2> warning.log

としておけば、

  • normal.log には通常ログのみ
  • warning.log には警告やエラーのみ

が記録されます。

このようにstdoutとstderrをきちんと分けておくことが、運用しやすいプログラムへの近道です。

まとめ

本記事では、C言語の標準入力(stdin)・標準出力(stdout)・標準エラー(stderr)について、基本的な役割からリダイレクト、バッファリング、実践的な使い分けまで解説しました。

stdinは主にscanfやfgetsで入力を受け取り、stdoutはprintfやputsで通常結果を出し、stderrにはfprintfでエラーメッセージを出すのが定番のスタイルです。

リダイレクトやパイプと組み合わせることで、同じプログラムを対話的にもバッチ処理でも活用でき、ログ管理もしやすくなります。

標準入出力の仕組みを理解し、信頼性と保守性の高いCプログラムを設計していきましょう。

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

URLをコピーしました!