いいね!では「標準入力(stdin)/標準出力(stdout)/標準エラー(stderr)」について、基礎から実務で役に立つ具体例まで 徹底的に分かりやすく 解説します。長めですが、順を追えば確実に理解できます。必要ならコードをコピーして試してみてください。
概観:stdin / stdout / stderr とは?
stdin(標準入力):プログラムが外部から入力を受け取るための既定の入力ストリーム。Cでは stdin(FILE *)でアクセス。UNIXレベルではファイルディスクリプタ 0 に対応。
stdout(標準出力):プログラムの通常出力先。Cでは stdout(FILE *)。UNIXでは FD 1。多くのコマンドで > やパイプ | によるリダイレクトで書き換えられる。
stderr(標準エラー):エラーメッセージ用の出力。stdout とは別で、デフォルトは端末に表示される。UNIXでは FD 2。
用途の違い:stdout は通常の結果、stderr はエラーや診断メッセージ(リダイレクトされない限り常に端末に出すことができる)に使うと便利。
端末(tty)/ファイルの違い:
stdout は「端末につながっているとき(tty のとき)」は 行バッファ、ファイルやパイプに繋がれていると 全体(ブロック)バッファ になるのが一般的。
stderr は デフォルトでアンバッファ(ほぼ即時出力)。
Cでの基本 API(使うヘッダ)
#include <stdio.h> // FILE*, stdin, stdout, stderr, fgets, printf, fopen, fread, fwrite, fclose, fflush, fseek, ftell, perror, fileno, setvbuf, setbuf #include <stdlib.h> // malloc, free, exit #include <errno.h> // errno, perror, strerror #include <unistd.h> // isatty, fileno, dup2 (POSIX) バッファリング(最も重要!)
unbuffered(無バッファ):出力は即座に行われる(stderr は通常この扱い)。
line-buffered(行バッファ):改行が来るまでバッファに貯める。端末に接続されている stdout がこのモードになることが多い。
fully-buffered(ブロックバッファ):バッファが満杯になるか fflush/fclose でフラッシュされるまで出力されない(ファイルやパイプに接続された stdout 等)。
制御:
fflush(FILE *stream):指定ストリームを強制フラッシュ。fflush(NULL) で全ストリームをフラッシュ。
setbuf(stream, NULL):ストリームを無バッファにする(簡易)。
setvbuf(stream, buf, mode, size):細かく制御(_IONBF, _IOLBF, _IOFBF)。
注意:インタラクティブなプロンプトを出すときは printf("Prompt: "); fflush(stdout); を忘れない。でないとプロンプトが表示されず入力待ちになることがある。
入出力の基本例(超シンプル)
getchar() / putchar() を使ったエコー(ファイルやパイプでも動く):
int main(void) {
int c; // getcharはEOFを表現するため int を使う
while ((c = getchar()) != EOF) {
if (putchar(c) == EOF) {
perror("putchar");
return 1;
}
}
if (ferror(stdin)) {
perror("stdin read error");
return 1;
}
return 0;
}
ポイント:char ではなく int を使う(EOF のため)。
fgets() を使った行読み(安全)+改行除去例:
int main(void) {
printf("名前を入力してください: ");
fflush(stdout); // プロンプトを確実に表示
if (!fgets(buf, sizeof buf, stdin)) return 0;
printf("こんにちは、%sさん\n", buf);
return 0;
}
※ gets() は危険で 廃止。絶対に使わない。
scanf() の落とし穴と安全な代替
scanf("%d", &i) は入力不正でバッファ残ったり、戻り値チェックしないと危険。
安全策:fgets() で行を取り、strtol() / sscanf() で解析する(エラー判定がしやすい)。
例(整数を安全に読む):
int main(void) {
if (!fgets(line, sizeof line, stdin)) return 0;
errno = 0;
char *end;
long v = strtol(line, &end, 10);
if (end == line || (*end != '\n' && *end != '\0')) {
fprintf(stderr, "数値でありません\n");
return 1;
}
if ((v == LONG_MIN || v == LONG_MAX) && errno == ERANGE) {
fprintf(stderr, "範囲外\n");
return 1;
}
printf("読み取った値 = %ld\n", v);
return 0;
}
バイナリ入出力(fread / fwrite)
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
使い方の基本(チャンクでコピーする例):
int main(void) {
FILE *in = fopen("in.bin", "rb");
FILE *out = fopen("out.bin", "wb");
if (!in || !out) { perror("fopen"); return 1; }
size_t n;
while ((n = fread(buf, 1, sizeof buf, in)) > 0) {
if (fwrite(buf, 1, n, out) != n) { perror("fwrite"); return 1; }
}
if (ferror(in)) perror("fread");
fclose(in); fclose(out);
return 0;
}
注意点:
fread/fwrite の戻り値は 要素数(ここでは nmemb)なので、失敗チェックを忘れない。
バイナリを扱う場合は Windows で改行変換されないよう b モード("rb" / "wb")を使う。
構造体を丸ごと fwrite(&s, sizeof s, 1, fp) で保存する方法は簡単だが、パディング・エンディアン・型サイズの違いで可搬性が失われる。異なる環境で読み書きするならフィールド単位で明示的に(固定幅整数+バイトオーダ統一)シリアライズする。
低レベル(POSIX): file descriptor / read / write / dup2
FILE* は高レベルの stdio ライブラリ。内部でバッファ処理をしてくれて便利。
低レベルではファイルディスクリプタ(整数)を使い、read/write(UNIX)で入出力する。パフォーマンスや細かい制御が必要な場合に使う。
例:プログラム内で stdout をファイルにリダイレクトする(POSIX):
int main(void) {
int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); return 1; }
if (dup2(fd, STDOUT_FILENO) < 0) { perror("dup2"); return 1; }
close(fd); // dup2 で stdout に複製済み
printf("この行は out.txt に書き込まれる\n");
return 0;
}
※ Windows では別API(_open, _dup2 等)や freopen を使う。
freopen は stdout を別ファイルに向ける簡単な方法:
freopen("out.txt", "w", stdout);
printf("out.txt に出力\n");
リダイレクト/パイプ(シェルとの関係)
./prog < input.txt — stdin がファイル input.txt に置き換わる。
./prog > out.txt — stdout が out.txt に書き込まれる(上書き)。
./prog >> out.txt — stdout を追記。
cmd1 | cmd2 — cmd1 の stdout が cmd2 の stdin に繋がる(パイプ)。
重要:stdout がファイル/パイプに繋がると行バッファではなくブロックバッファになるので、改行なしの出力がすぐに見えないことがある。
EOF とエラー判定
キーボードでの EOF:UNIXでは Ctrl-D、Windowsでは Ctrl-Z(+ Enter)で送る。
fread / fgets / getchar が失敗したら feof(stream)(EOF 到達)や ferror(stream)(I/O エラー)で判定する。
feof は 直ちに EOF を返すわけではなく、読み込み試行のあとで確認する(試行前に true にはならない)。
例(読み込みループの正しい書き方):
while (fgets(buf, sizeof buf, fp) != NULL) {
// 読み取り成功
}
if (ferror(fp)) {
perror("read error");
}
stdout のフラッシュに関する重要な注意
対話型プログラムでプロンプトが表示されない → fflush(stdout) を確認。
プログラムがクラッシュするとバッファに残った stdout のデータは消えることがある(stderr は多くの場合即時表示なので重要なログは stderr に出すことを検討)。
exit() や return により正常終了すると通常は自動で stdout がフラッシュされるが、クラッシュや _exit() ではフラッシュされない。
その他の便利関数・ポータビリティ
getline(&lineptr, &n, stdin):行を動的に読み込む(POSIX)。バッファを自動拡張してくれるので便利。Windowsでは標準Cに無いので注意。
fileno(FILE *stream):FILE* から低レベルの FD を得る。isatty(fileno(stdin)) で端末接続確認ができる。
perror("msg"):errno に基づくエラーメッセージを stderr に出す。fprintf(stderr, ...) と組み合わせる。
snprintf:バッファオーバーフローを防ぐフォーマット出力(sprintf は危険)。
実践的な「よくある問題」と対策まとめ
プロンプトが表示されない → fflush(stdout); を入れる。
入力が途中で壊れる/数字読み込みが失敗 → fgets+strtol/strtod で厳密に検証。
バイナリファイルが壊れる(Windows) → fopen(..., "rb"/"wb") を使う。
構造体丸ごと保存して別環境で読めない → フィールドごとに明示的にシリアライズ(バイトオーダを統一)。
エラーメッセージもリダイレクトしたい → fprintf(stderr, "...") の代わりに stdout に出す選択も検討(だが通常は stderr)。
速度が出ない → 大きめのバッファ(BUFSIZ 以上)を setvbuf で設定、あるいは低レベル read/write を検討。
便利なサンプル集(まとめて置く)
1) プロンプト → 入力(改行削除) → 出力
int main(void) {
printf("好きな文字列を入力してください: ");
fflush(stdout);
if (fgets(buf, sizeof buf, stdin)) {
printf("あなた: %s\n", buf);
}
return 0;
}
2) 標準入力全文を読み取って標準出力へ(cat風)
int main(void) {
size_t n;
while ((n = fread(buf, 1, sizeof buf, stdin)) > 0) {
if (fwrite(buf, 1, n, stdout) != n) {
perror("fwrite");
return 1;
}
}
if (ferror(stdin)) { perror("stdin read"); return 1; }
return 0;
}
3) getline を使った行処理(POSIX)
int main(void) {
char *line = NULL;
size_t cap = 0;
ssize_t len;
while ((len = getline(&line, &cap, stdin)) != -1) {
printf("len=%zd: %s", len, line);
}
free(line);
return 0;
}
4) stdin が端末かどうか判定して挙動を変える
int main(void) {
if (isatty(fileno(stdin))) {
puts("interactive mode");
} else {
puts("non-interactive (piped/file) mode");
}
return 0;
}
最後に:実務でのチェックリスト(すぐ使える)
入力は fgets、解析は strtol/strtod でエラー検出。
出力プロンプトの後は fflush(stdout)。
エラーメッセージは stderr。
バイナリは "rb"/"wb"、構造体の生保存は可搬性に注意。
リダイレクト/パイプ時のバッファリング変化を意識(端末か否かを判定)。
fread/fwrite の戻り値を必ずチェック。
getchar() や fgetc() の戻りは int。EOF 判定を忘れない。
もっと踏み込んで欲しい内容(必要ならやります):
A. 「構造体をポータブルにシリアライズ」の完全実装(バイトオーダ変換+可変長文字列)
B. 「標準入出力のパフォーマンス改善(setvbuf、read/write 比較)」の実測コード
C. 「パイプ / popen / 子プロセスとの入出力」のサンプル(subprocess連携)
D. 「Windowsでの違い(CRLF、_open/_fdopen、setmode)」
どれを先に出そうか?それとも今の説明で不明な箇所をピンポイントで掘り下げる?必要なら具体的な用途(例:「対話型CLI作りたい」「大量バイナリ処理で高速化したい」など)を教えてくれれば、それに合わせた例を先に作ります。