printfデバッグとバッファリング

土曜日に後輩に頼まれてCのプログラムを見たところ、次の様なコードが。

main() {
    /* 処理1 */

    printf("!");

    /* 処理2 */

    printf("!");

    /* 処理3 */
}

これで、セグメンテーションフォルトが起こるので、!の数を数えてみると、結果として1つだけ出力される。
そのため、処理2にエラーがあると思ってバグを探したけれど、バグが見つからなかったそうだ。


このように、printfなどで出力結果を見ながらデバッグを行う方法は、それなりに使える。しかし、そのためにはprintfの仕様を理解していなくてはならない。


printfの出力結果は、一般的にバッファへ保持される。
つまり、関数を呼べばすぐに結果がコンソールに出力されるわけではない。
バッファの内容をきちんとコンソールへ出力することを保証しなければならないのである。
コンソールへ出力するための方法としては、以下の3種類の方法があげられる。

  • バッファサイズ以上の文字を出力する
  • fflush(stdout)で、強制的にバッファを書き出す
  • 改行コードを入れる

これらの方法だが、一般的には改行コードを入れるだけで問題が無い。上記の例では、実は処理2で改行コードを出力していたため、!が1つだけ表示されていたのである。


では、改行コードを入れれば確実かというと、実はそうでもないのである。*1
stdoutはFILE構造体のデータであり、FILE構造体は標準でバッファを持っている。デフォルト値は何処で設定されるかは今回のエントリを書くに当たっては見つけることが出来なかった。
FILE構造体がどのようにバッファを使用するかは、setvbuf(FILE*, char*, int, size_t)を用いることで変更できる。この関数の第三引数に

  • _IONBF(バッファを使用しない)
  • _IOLBF(改行コードでバッファ出力)
  • _IOFBF(バッファがあふれるまでバッファリングを行う)

のどれかを渡すことで、そのモードを変更できる。ただし、変更はバッファが使われる前に行わなければならない。


とは言っても、基本的には改行を行う事で出力するモードになっているはずである。……と思い、VC++2005で実行してみたのだが、これは_IONBFモードで実行されている。
しかも、モード変更してみても、なぜか_IOLBFが期待した出力をしない(_IOFBFと同じ動作となる)
こいつについては、情報求む。


printfデバッグを使用する際には、「最後に改行を付ける」ことをすれば、おそらく問題無いが、出力がおかしい場合はfflush(stdout)を付けて確かめることを心にとどめておく必要がある。
というのが本エントリの一応の結論です。


なお、id:bleis-tift氏の協力により、C++を調べて(貰った)ところ、C++では、std::endlでフラッシュ(コンソールへの出力)されるそうです。

    // フラッシュされないかもしれない
    std::out << "Hoge\n";
    // フラッシュされる
    std::out << "Hoge" << std::endl;

*1:調べる前は、上記3種のどれかでOKかと思っていた