プログラムの出力が「すぐに表示されない」「順序が入れ替わった」経験はありませんか。
原因の多くはバッファリングです。
本記事では、setvbuf
を使って標準出力(stdout)と標準エラー出力(stderr)のバッファリングを意図通りに制御する方法を、初心者向けに丁寧に解説します。
setvbufの基本とstdout・stderr
バッファリングとは何か
入出力は1文字ずつOSに依頼すると遅くなります。
そのためCライブラリは一時領域(バッファ)にデータを貯めてからまとめて書き込みます。
これがバッファリングです。
モードは概ね次の3種類があります。
- 全バッファリング(
_IOFBF
): バッファがいっぱいになったときや明示的にフラッシュしたときにまとめて書き込みます。 - 行バッファリング(
_IOLBF
): 改行('\n'
)が出力されたときなどに書き込みます。 - 無バッファ(
_IONBF
): 出力関数の呼び出しごとに即時に書き込みます。
バッファリングは速度向上の代わりに見かけ上の「遅延」を生みます。
インタラクティブなプロンプトや進捗表示では遅延は不便なので、状況に応じて制御します。
stdoutとstderrの既定の違い
多くの実装では標準出力と標準エラー出力で既定のモードが異なります。
目安は次の通りです(実装依存です)。
ストリーム | 端末に接続(例: コンソール) | リダイレクト/パイプ/ファイルへ |
---|---|---|
stdout | 行バッファリングになることが多い | 全バッファリングが多い |
stderr | 常に無バッファが多い | 常に無バッファが多い |
このため、エラーはすぐ出るのに通常メッセージは遅れて出るという状況がよく起きます。
フラッシュのタイミング
フラッシュ(実際の書き込み)は次のようなタイミングで起きます。
_IOFBF
: バッファ満杯、fflush
、fclose
、プログラム正常終了時_IOLBF
: 改行('\n'
)を書いたとき、バッファ満杯、fflush
、fclose
_IONBF
: ほぼ即時- さらに、多くの実装で端末から入力を行う直前に、行バッファの出力が自動的にフラッシュされます(規格上の詳細は実装依存です)。
「今すぐ出したい」と思ったらfflush(stream)
を使えば確実です。
setvbufの使い方
書式と引数
setvbuf
はストリームのバッファリング方法を設定します。
使う前に必ず#include <stdio.h>
を記述します。
// 標準ライブラリの宣言 (C標準)
int setvbuf(FILE *restrict stream,
char *restrict buf,
int mode,
size_t size);
/*
引数:
stream : 対象ストリーム(stdout, stderr, fopenで得たFILE*)
buf : 利用するバッファへのポインタ(NULLならライブラリに任せる)
mode : _IOFBF(全), _IOLBF(行), _IONBF(無)
size : バッファサイズ(バイト)。_IONBFのときは無視される
戻り値:
0なら成功、0以外は失敗(設定できなかった)
注意:
"そのストリームで最初の入出力を行う前"に呼び出すこと
*/
成功しても実装が希望を完全に反映する保証はありません(サイズが調整されるなど)。
ただしモード指定は尊重されるのが一般的です。
モードの選び方
対話型ツールやCLIの標準出力
- 対話的に1行ずつ見せたいときは行バッファリング(
_IOLBF
)が扱いやすいです。改行で自動フラッシュされます。 - 1行の終わり以外でも即時性が必要なら無バッファ(
_IONBF
)またはfflush(stdout)
を併用します。
端末では既定で行バッファになることも多いですが、明示しておくと挙動が安定します。
モードの選び方
ログやエラーの標準エラー出力
- stderrは無バッファ(
_IONBF
)が基本です。クラッシュ直前のメッセージも失われにくくなります。 - 大量のエラーを高速に書きたい場合のみ、
_IOLBF
や_IOFBF
を検討しますが、取りこぼしリスクが増えます。
モードの選び方
バッチ処理・ファイル出力
- 大容量の書き出しやリダイレクト先がファイルのときは全バッファリング(
_IOFBF
)が最も効率的です。 - パイプで次段に素早く渡したいなら
_IOLBF
も選択肢です。 - 性能重視なら
_IOFBF
、即時性重視なら_IONBF
、見やすさ重視なら_IOLBF
という指針で考えると整理しやすいです。
バッファの指定方法
buf
にNULL
を渡すと、実装が内部的にバッファを用意します。
自前バッファを渡す場合はプログラムがそのストリームを閉じるまで寿命が続くメモリ(静的領域やmalloc
確保領域)を使います。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 1) ライブラリに任せる(おすすめの基本形)
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
perror("setvbuf(stdout)");
return 1;
}
// 2) 自前の固定バッファを使う例(寿命に注意)
static char errbuf[BUFSIZ]; // 静的ストレージ期間で安全
if (setvbuf(stderr, errbuf, _IONBF, sizeof errbuf) != 0) {
perror("setvbuf(stderr)");
return 1;
}
printf("設定完了\n");
return 0;
}
バッファサイズ(size)の考え方
BUFSIZ
は実装が推奨する典型サイズです(多くは8KB程度)。- 大きくしても効果はケースバイケースです。巨大にしてもシステムコール回数が大幅に減らないこともあります。
- 小さくしすぎると性能が落ちます。迷ったらデフォルト(0指定で実装に任せる、または
BUFSIZ
)が無難です。 _IONBF
のときsize
は無視されます。
注意点とよくあるミス
最初の入出力前に呼ぶこと
setvbufは、そのストリームで最初の入出力を行う前に呼ぶ必要があります。
一度でもprintf
やfprintf
などを呼んだ後に設定しようとすると、失敗したり未定義動作になることがあります。
#include <stdio.h>
int main(void) {
// 正しい: 最初の入出力の前に設定
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
perror("setvbuf");
return 1;
}
printf("最初のメッセージ\n");
return 0;
}
実行結果(一例)
最初のメッセージ
自前バッファの寿命に注意
自前バッファにローカル配列(自動記憶域)を渡すのは危険です。
関数から抜けると寿命が切れ、未定義動作になります。
静的配列またはmalloc
で確保し、fclose
まで解放しないようにします。
// 悪い例: 関数を抜けるとbufは無効になる
void bad(void) {
char buf[BUFSIZ]; // ローカル(寿命が短い)
setvbuf(stdout, buf, _IOLBF, sizeof buf); // 未定義動作の原因
}
fflushで即時に出力する
「今この瞬間に見せたい」時はfflush(stream)
を呼びます。
特にプロンプトや進捗のように改行を付けない表示は、行バッファでも自動では出ません。
#include <stdio.h>
int main(void) {
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
perror("setvbuf");
return 1;
}
printf("名前を入力してください: "); // 改行なし
fflush(stdout); // ここで必ず出す
// ここで入力処理を行う(fgetsやscanfなど)
// ...
printf("\n入力ありがとうございました。\n");
return 0;
}
実用例
stdoutを行バッファリングにする
端末への出力を1行ごとに確実に表示させたい例です。
改行で自動フラッシュされます。
#include <stdio.h>
int main(void) {
// stdoutを行バッファリングに設定(バッファは実装に任せる)
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
perror("setvbuf(stdout)");
return 1;
}
// 各行は改行で即座に表示される
printf("処理を開始します...\n");
printf("ステップ1完了\n");
printf("ステップ2完了\n");
printf("すべて完了\n");
return 0;
}
実行結果(一例)
処理を開始します...
ステップ1完了
ステップ2完了
すべて完了
stderrを無バッファにする
エラーはすぐ見せたいのでstderrを無バッファにします。
stdoutと混在させるときは順序に注意し、必要に応じてfflush(stdout)
を併用します。
#include <stdio.h>
int main(void) {
// stderrを無バッファに設定
if (setvbuf(stderr, NULL, _IONBF, 0) != 0) {
perror("setvbuf(stderr)");
return 1;
}
printf("通常メッセージA"); // 改行なし(まだ出ない可能性)
fprintf(stderr, "エラーB\n"); // 即時に出る
printf("通常メッセージC\n"); // ここでまとめて見えることがある
// stdoutの順序を保証したい場合はfflushを使う
// fflush(stdout);
return 0;
}
実行結果(一例・端末)
通常メッセージAエラーB
通常メッセージC
上の順序は一例です。
リダイレクトや環境によりstdout側は遅れて表示されることがあります。
進捗表示を即時に見せる
進捗バーのように改行を付けずに上書きする表示では、都度fflush(stdout)
を呼ぶか_IONBF
にします。
ここでは行バッファリングに設定しつつfflush
で確実に出します。
#include <stdio.h>
int main(void) {
// 行バッファに設定(ここでは改行が無いので自動では出ない)
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
perror("setvbuf(stdout)");
return 1;
}
for (int p = 0; p <= 100; p += 20) {
printf("\r進捗: %3d%%", p); // 改行なしで上書き表示
fflush(stdout); // ここで即時に見せる
// 簡易な待ち時間(移植性のため空ループで擬似ウェイト)
for (volatile long i = 0; i < 100000000; ++i) { /* wait */ }
}
printf("\n完了!\n");
return 0;
}
実行結果(一例・端末)
進捗: 0% -> 20% -> 40% -> 60% -> 80% -> 100%
完了!
常に即時表示したいならsetvbuf(stdout, NULL, _IONBF, 0)
も有効です。
ただし無バッファは性能が下がる可能性があるため、fflush
併用のほうが実用的なことが多いです。
まとめ
出力の見え方を決めるのはバッファリングです。
Cではsetvbuf
で全(_IOFBF
)・行(_IOLBF
)・無(_IONBF
)を選べます。
stdoutは状況により既定が変わるのに対し、stderrは即時性重視が通例です。
設定は最初の入出力前に行い、自前バッファを使うなら寿命に注意しましょう。
プロンプトや進捗などではfflush
を忘れず、必要に応じて_IOLBF
や_IONBF
を使い分けることで、見やすく、正しく、速い出力が実現できます。