MinGW-w64の標準出力をバッファリングしないように設定する。
高木です。こんばんは。
私はWindowsでCやC++を使うときには、C++/CLIを使うとかでなければMinGW-w64を使っています。
簡単なものならMsys2上でコンパイルしますし、ちょっと複雑になるとIDEとして(Qtを使わなくても)Qt Creatorを使います。
ちょっとしたコマンドラインツールを作るときなどは、printf関数なんかも多用することになります。
GUIを作るときにもprintfデバッグはやりますから、いずれにしても標準出力はなくてはならない存在です。
ところが、MinGW-w64の標準出力はデフォルトではフルバッファリングになっているようなのです。
これはかなり不便です。
対話的なプログラムを作ろうと思うと、いちいちfflush関数を呼び出してバッファに溜まった内容を吐き出すようにしないといけません。
たとえば、プロンプトを表示して、値を入力させて、という単純なことをやるだけでもこの問題に直面するのです。
C++であれば、std::endlやstd::flushでフラッシュさせるのでまだマシです。
問題はCなのです。
以前はいちいちfflush関数を呼び出していました。
しかし、さすがに面倒です。
そこで、最近ではプログラム開始時にsetvbuf関数を使って_IONBFを設定するようにしています。
これで明示的にバッファリングを抑止することができます。
main関数の最初でいちいちsetvbuf関数を呼ぶのは面倒ですし、MinGW-w64以外の環境に移植したときには目障りです。
そこで、次のような手を考えました。
1 2 3 4 5 6 7 | #include <stdio.h> __attribute__((constructor)) void init_stdio() { setvbuf(stdout, NULL, _IONBF, 0); } |
といった内容のコードをコンパイルして、あらかじめinit_stdio.oというオブジェクトファイルを作っておきます。
これを明示的にリンクしてやれば、main関数より先にinit_stdio関数が呼ばれて初期化を行うことができます。
Visual C++では#pragma init_segを使いますが、GCCなら__attribute__((constructor))でこの指定ができます。
さて、問題はここからです。
本当なら、標準出力がコンソールへの出力のときだけバッファリングを抑止したいのです。
ファイルにリダイレクトする場合には、これまで通りフルバッファリングで構いません。
こういうときには、isatty関数を使ってコンソールへの出力になっているかどうかを判別するのがセオリーでしょう。
MinGW-w64でもisatty関数は使えますし、コマンドプロンプトやPowerShellでは期待通りに動作します。
ところが、Msys2のbashで試すとisatty関数は常に0を返します。
これでは判別ができません。
実装は追っていませんが、おそらくbashは標準出力をいったんパイプで受けて処理しているのでしょうね。
よく考えると、Qt CreatorなどのIDEから使おうとしたときも、デバッガは標準出力をパイプで受けて画面に出力しようとするはずです。
これではやはり同じ問題が起きてしまいます。
しかたがないので、問答無用で標準出力はバッファリングなしで使うように設定したいと思います。
どうしても実行時に切り替えたいときは、コマンドライン引数で明示的に指定するか、環境変数でも使うしかないでしょうね。
どうせ判別ができないのであれば、いっそのこと標準入力、標準出力、標準エラーの3つは問答無用でバイナリモードに変えてしまった方がいいかもしれません。
bashやQt Creatorのアプリケーション出力は、改行文字がCR LFではなく、単なるLFでも問題なく改行してくれますから。
ちなみに、オープン済みのストリームを強制的にバイナリモードに変えるには_setmode関数を使います。
1 | _setmode(fileno(stdout), O_BINARY); |
こんな感じですね。
このケースに限っては、fileno(stdout)は整数定数の1を指定してしまってもいい気がします。