system関数とpopen関数を適当に選んではいけない

CやC++で、プログラムから他のコマンドを実行する方法はいくつかあると思います。

それらの内、system関数やpopen関数を使うのが、とりわけ簡単かと思いますが、これらを適当に選んではいけない、という事態に遭遇したので、せっかくなのでsystem関数とpopen関数の違いや選び方とかを交えながらブログのエントリにしておきます。

まず、system()とpopen()の違いを簡単に言うと、コマンドを実行する際、標準入力に書き込んだり標準出力への出力を利用したりしたい場合はpopen()を、ただ実行すれば良いだけなら、system()を使うべきです。

標準入力/出力が使える、という機能が増えているだけなら、いつでもpopenでもいいじゃないか!と思いきや、そこに罠(!?)があったのです!

popen()を使う際は、popen()の戻り値として開かれたストリームをpclose()で閉じる必要があります。

ここで、pclose()のソースコードを見てみると

pcloseのソースコード

なんと、ストリームを閉じた後にプロセスの終了待ちをしています!

そこで、以下のwriteTestというコマンドを、system()とpopen()それぞれを使って呼び出す実験をしてみます。

popen.cpp / system.cpp / writeTest.cpp

 


$ ls
popen.cpp system.cpp writeTest.cpp
$ g++ writeTest.cpp -o writeTest
$ g++ popen.cpp -o popen
$ g++ system.cpp -o system
$ ls
popen.cpp popen system.cpp system writeTest.cpp writeTest
$ ./popen
Caller starts
Caller ends
$ ls
popen.cpp popen system.cpp system writeTest.cpp writeTest
$ ./system
Caller starts
Callee starts
Callee ends
Caller ends
$ ls
popen.cpp popen system.cpp system test.txt writeTest.cpp writeTest

popen()のパターンでは、Callee startsとCallee endsはpopen()によって開かれたストリームに書き込まれるので当然表示されないとしても、実行後にtest.txtというファイルが生成されていません。
一方、system()のパターンでは実行後にtest.txtというファイルが生成されているのがわかるかと思います。
なぜこうなるかというと、writeTestコマンドの最初の

std::cout << "Callee starts" << std::endl;

の実行時に、すでにpclose()によってストリームが閉じられているので、そこでコマンドが失敗しているのです。このスケジューリングはOSによって決まると思うので、100%ではありません。

なので、場合によっては、pclose()が実行される前に

std::cout << "Callee starts" << std::endl;

だけ通過してしまえば、popen()のパターンでもtest.txtが生成されます。

最初、この現象に遭遇した時は、popen()がプロセスの終了待ちをしてくれないのだろうか!?と思ったのですが、そういうわけではありませんでした。
しかし、いずれにしても、コマンド実行時は、標準入力/出力が必要ないなら、コードもスッキリするので、system()を使うべきでしょう。


「system関数とpopen関数を適当に選んではいけない」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。