- blogs:
- cles::blog

printf() のバッファリングにはまる

昨日に引き続き後輩がプログラムが想定通りに動かないと相談に来たので、一緒にデバッグ。
開発しているプログラムはJavaのプログラムからRuntime.exec()経由でC言語のプログラムを起動し、開いたInput / Output Streamを使って起動したプログラムと対話するというというもの。どうも対話がうまくいかなくて、途中で詰まってしまうとのこと。
双方向のパイプを使ってプログラムを操作しようとして、バッファリングのせいでデッドロックになるとか、プログラムが終了するまでログファイルになにも出力されないというようなハマり方はこれまで個人的に散々やらかしていたので、すぐにこれがバッファリングの問題であろうという見当はつきました。ちなみにブログ内検索をしたら、去年もPythonでハマった記録がありますね。
C言語は普段あまり使わないので、printfも標準でバッファリングが行われているというのは恥ずかしながら認識していませんでした。
† 検証用のプログラムを書いてみる
このあたりの問題は、例えば下記のような単純なプログラムでも確認することができます。
このプログラムをコマンドラインから二通りの方法で、起動させてみると不思議なことが起こります。
前者は標準出力、標準エラーがターミナルにつながっているもので、後者はパイプを通して他のプログラムにつながっているものですが、パイプを通したものはバッファリングによって、出力順序が入れ替わっていることが確認出来ます。これは、後述しますがバッファリングについては出力先がターミナルかそうでないかによって挙動が変わるような仕様になっているためです。
† バッファリングを無効化する
ストリームのバッファを無効にする方法については、Perlの「$| = 1;」とか、Rubyの「$stdout.sync = TRUE」というのはすぐに思い浮かんだのですが、さすがにCは普段使っていないせいか良くわかりませんでした。
ちなみにこれはprintf()ではなくて、stdoutの性質に依存する問題なのでprintf()にはバッファリングの解説はありません。解決の糸口がないので「printf バッファ 無効」というキーワードでウェブを検索してみるとsetvbuf()を使えば出来るという話が沢山出てきたので、setvbufをmanであたってみるたところ、詳しい使い方が出てきました。
これによると、前述の標準エラーはバッファリングがデフォルトで無効であることや、出力先がターミナルの場合にはブロックバッファでなく、ラインバッファになっていることが確認できます。
Manpage of SETBUF
通常、ファイルはすべて block buffered である。ファイルに対して 初めて入出力処理を行うと malloc(3) が呼び出されバッファが獲得される。もし ストリームが (通常、 stdout がそうであるように) ターミナルを参照する場合には、ファイルは line buffered と なる。標準エラー出力 stderr はデフォルトでは常に unbuffered である。
結論としては、printfのバッファを無効にするためには、バッファを無効にする_IONBFを指定した下記の一行をプログラムに足せばいいようです。バッファは無効にしているので、バッファの領域にはNULL、長さは0を指定しています。
ということで、修正後の下記のようなプログラムに対して、同様の実験をしてみます。
こうすることで、バッファが無効になり、ターミナルでもそうでなくても挙動の差をなくす事ができました。
普段、バッファがフラッシュされるタイミングはあまり意識することは少ないのですが、他のプログラムと双方向のパイプでデータをやりとりするときには思わぬハマりポイントになりやすいので注意したいところです。
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3194
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
自前の C++ のログクラスでは vfprintf とか使ってますが、その後に fflush(fp) で強制的に吐き出すようにしてます。
なるほど。考えてみたらflushするのが順当なんですよね。
今回はデバッガのフロントエンドを開発してるんで、元のプログラムには手を加えてはいけないという制約があっていろいろと面倒な事になっています。
コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。
OpenID を使ってログインすることができます。
2 . 福岡銀がデマの投稿者への刑事告訴を検討中(110736)
3 . 年次の人間ドックへ(110340)
4 . 2023 年分の確定申告完了!(1つめ)(109893)
5 . 三菱鉛筆がラミーを買収(109792)