1. ホーム
  2. c

[解決済み] fork()後のprintfの不具合

2023-05-04 06:56:01

質問

OS:Linux Linux、言語:ピュアC

私は一般的なCプログラミング、そして特殊なケースとしてUNIXでのCプログラミングを学ぶために前進しているのです。

私は、(私にとって)奇妙な振る舞いを検出しました。 printf() を使用した後に fork() を呼び出します。

コード

#include <stdio.h>
#include <system.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d", getpid() );

    pid = fork();
    if( pid == 0 )
    {
            printf( "\nI was forked! :D" );
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}

出力

Hello, my pid is 1111
I was forked! :DHello, my pid is 1111
2222 was forked!

なぜ2つ目の "Hello" の文字列が子供の出力に現れたのでしょうか?

はい、それはまさに親が起動時に出力したものであり、親の pid .

しかし!もし \n 文字を各文字列の末尾に置くと、期待通りの出力が得られます。

#include <stdio.h>
#include <system.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() ); // SIC!!

    pid = fork();
    if( pid == 0 )
    {
            printf( "I was forked! :D" ); // removed the '\n', no matter
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}

出力 :

Hello, my pid is 1111
I was forked! :D
2222 was forked!

なぜこのようなことが起こるのでしょうか?正しい動作なのか、それともバグなのでしょうか?

どのように解決するのですか?

私は、以下のことに注目しています。 <system.h> は非標準のヘッダであることに注意してください。 <unistd.h> に置き換えたら、きれいにコンパイルされました。

プログラムの出力がターミナル(画面)に向かうときは、ラインバッファリングされます。 プログラムの出力がパイプに向かう場合は、フルバッファリングされます。 バッファリングモードを制御するには、標準C関数 setvbuf()_IOFBF (フルバッファリング) を使用します。 _IOLBF (ラインバッファリング) および _IONBF (バッファリングなし) モードがあります。

修正したプログラムでこれを実証するには、プログラムの出力をパイプで、例えば cat . の末尾に改行があっても printf() の文字列の最後に改行があっても、二重の情報を見ることになる。 ターミナルに直接送信した場合は、1つのロットの情報だけが表示されます。

この話の教訓は、注意深く fflush(0); を呼び出して、フォークする前に全てのI/Oバッファを空にすることに注意することです。


要求された行ごとの分析(中括弧などを削除し、マークアップ エディターによって先頭の空白を削除)。

  1. printf( "Hello, my pid is %d", getpid() );
  2. pid = fork();
  3. if( pid == 0 )
  4. printf( "\nI was forked! :D" );
  5. sleep( 3 );
  6. else
  7. waitpid( pid, NULL, 0 );
  8. printf( "\n%d was forked!", pid );

分析が

  1. "こんにちは、私の pid は 1234 です" を標準出力用のバッファにコピーしています。 最後に改行がなく、出力はライン バッファ モード (またはフル バッファ モード) で実行されているため、ターミナルには何も表示されません。
  2. stdout バッファに全く同じ内容を持つ、2 つの別々のプロセスを与えます。
  3. 子プロセスには pid == 0 を持ち、4行目と5行目を実行します。親はゼロ以外の値の pid の戻り値がゼロでないことです (この 2 つの処理の数少ない違いの 1 つは getpid()getppid() はさらに2つ)。
  4. 子の出力バッファに改行と "私はフォークされました!:D" を追加します。 出力の最初の行は端末に表示されます。出力はラインバッファリングされているため、残りはバッファに保持されます。
  5. すべてが 3 秒間停止します。 この後、子プロセスは main の最後にある return を通して通常通り終了します。 このとき、stdout バッファに残っているデータはフラッシュされます。 これは、改行がないため、出力位置が行末のままである。
  6. 親はここに来る。
  7. 親は子供が死に終わるのを待ちます。
  8. 親は出力バッファに改行と "1345 was forked!" を追加します。 改行は、子プロセスによって生成された不完全な行の後に、 'Hello' メッセージを出力にフラッシュします。

最後にまだ改行がないため、カーソル位置は感嘆符の後になり、シェルプロンプトが同じ行に表示されます。

私が見たものは

Osiris-2 JL: ./xx
Hello, my pid is 37290
I was forked! :DHello, my pid is 37290
37291 was forked!Osiris-2 JL: 
Osiris-2 JL: 

PID番号は異なりますが、全体的な外観は明らかです。 の末尾に改行を追加する。 printf() 文の最後に改行を加える (これはすぐに標準的な習慣になります) と、出力が大きく変わります。

#include <stdio.h>
#include <unistd.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() );

    pid = fork();
    if( pid == 0 )
        printf( "I was forked! :D %d\n", getpid() );
    else
    {
        waitpid( pid, NULL, 0 );
        printf( "%d was forked!\n", pid );
    }
    return 0;
}

今、私は手に入れた。

Osiris-2 JL: ./xx
Hello, my pid is 37589
I was forked! :D 37590
37590 was forked!
Osiris-2 JL: ./xx | cat
Hello, my pid is 37594
I was forked! :D 37596
Hello, my pid is 37594
37596 was forked!
Osiris-2 JL:

出力がターミナルに送られるとき、ラインバッファリングされることに注意してください。 fork() の前に表示され、1つだけコピーされていることに注意してください。 出力がパイプで cat にパイプされたとき、それは完全にバッファリングされているので、何も fork() の前に何も表示されず、両方のプロセスで 'Hello' 行がフラッシュされるバッファにあります。