- blogs:
- cles::blog
printfのバッファリングにはまる
昨日に引き続き後輩がプログラムが想定通りに動かないと相談に来たので、一緒にデバッグ。
開発しているプログラムはJavaのプログラムからRuntime.exec()経由でC言語のプログラムを起動し、開いたInput / Output Streamを使って起動したプログラムと対話するというというもの。どうも対話がうまくいかなくて、途中で詰まってしまうとのこと。
双方向のパイプを使ってプログラムを操作しようとして、バッファリングのせいでデッドロックになるとか、プログラムが終了するまでログファイルになにも出力されないというようなハマり方はこれまで個人的に散々やらかしていたので、すぐにこれがバッファリングの問題であろうという見当はつきました。ちなみにブログ内検索をしたら、去年もPythonでハマった記録がありますね。
C言語は普段あまり使わないので、printfも標準でバッファリングが行われているというのは恥ずかしながら認識していませんでした。
† 検証用のプログラムを書いてみる
このあたりの問題は、例えば下記のような単純なプログラムでも確認することができます。
int main(){
printf("line1\n");
fprintf(stderr, "line2\n");
printf("line3\n");
fprintf(stderr, "line4\n");
}
このプログラムをコマンドラインから二通りの方法で、起動させてみると不思議なことが起こります。
line1
line2
line3
line4
$ ./a.out | cat
line2
line4
line1
line3
前者は標準出力、標準エラーがターミナルにつながっているもので、後者はパイプを通して他のプログラムにつながっているものですが、パイプを通したものはバッファリングによって、出力順序が入れ替わっていることが確認出来ます。これは、後述しますがバッファリングについては出力先がターミナルかそうでないかによって挙動が変わるような仕様になっているためです。
† バッファリングを無効化する
ストリームのバッファを無効にする方法については、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を指定しています。
ということで、修正後の下記のようなプログラムに対して、同様の実験をしてみます。
int main(){
setvbuf(stdout, (char *)NULL, _IONBF, 0);
printf("line1\n");
fprintf(stderr, "line2\n");
printf("line3\n");
fprintf(stderr, "line4\n");
}
こうすることで、バッファが無効になり、ターミナルでもそうでなくても挙動の差をなくす事ができました。
line1
line2
line3
line4
$ ./a.out | cat
line1
line2
line3
line4
普段、バッファがフラッシュされるタイミングはあまり意識することは少ないのですが、他のプログラムと双方向のパイプでデータをやりとりするときには思わぬハマりポイントになりやすいので注意したいところです。
このエントリへのTrackbackにはこのURLが必要です→http://blog.cles.jp/item/3194
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
自前の C++ のログクラスでは vfprintf とか使ってますが、その後に fflush(fp) で強制的に吐き出すようにしてます。
なるほど。考えてみたらflushするのが順当なんですよね。
今回はデバッガのフロントエンドを開発してるんで、元のプログラムには手を加えてはいけないという制約があっていろいろと面倒な事になっています。
コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。
OpenID を使ってログインすることができます。
sp-20100319222942644595553@cles.net
- リングバッファ C言語
- Yahoo! - 10/03/19 16:33:33
- C言語 flush
- Yahoo! - 10/03/17 16:32:54
- setbuf malloc
- Google - 10/03/15 09:23:34
- C言語 printf バッファ 無効
- bing - 10/03/11 19:43:20
- C言語 printf 即時
- bing - 10/03/11 19:39:33
- C言語 バッファリング
- Yahoo! - 10/03/10 16:41:35
- C言語 FLUSH
- Yahoo! - 10/03/09 18:25:21
- fprintf fflash C言語
- Google - 10/03/04 10:48:34
- printf flush
- Google - 10/02/26 14:39:19
- ストリーム バッファリング Windows7
- Yahoo! - 10/02/26 11:16:01
- c言語 printf 〇 ●
- Yahoo! - 10/02/22 14:55:00
- C言語 リングバッファ
- Yahoo! - 10/02/22 12:01:02
- リングバッファ C言語
- Yahoo! - 10/02/20 16:29:30
- おめでとうございます (4)
- 知恵の輪 サターン編 (3)
- SourceForge.JPのSubversion... (3)
- 人生初の出来事 (3)
- サーバセットアップ (3)
- 和食 小錦 (3)
- 散髪しました (3)
- .inはインドのccTLDなのか (3)
- やっと髪をきりました (3)
- 大雪でした (3)
2 . やっぱりあった!パクれる読書感想文! [7623x]
3 . Echofon for Firefox [6388x]
4 . OpenIDで自分のサイトのURLを使う [5756x]
5 . 急性胃腸炎 [5743x]
- CD-ROM起動で、HDDを完全消去
- NP_Moblog v1.16
- pinzoro 01/15
- hsur 12/29
- and more...
- 耳がおかしいと思ったら突発..
- baca 01/13
- hsur 01/13
- and more...
★はてな認証APIをつかってログインすることができます。

http://kimitakeblog.net/



