web-dev-qa-db-ja.com

stdoutがファイルにリダイレクトされるときに明示的なフラッシュが必要なのはなぜですか?

printf()の動作はstdoutの場所に依存しているようです。

  1. stdoutがコンソールに送信されると、printf()はラインバッファリングされ、改行が出力された後にフラッシュされます。
  2. stdoutがファイルにリダイレクトされる場合、fflush()が呼び出されない限り、バッファーはフラッシュされません。
  3. さらに、stdoutがファイルにリダイレクトされる前にprintf()が使用された場合、以降の(ファイルへの)書き込みはラインバッファリングされ、改行の後にフラッシュされます。

stdoutはいつラインバッファリングされ、いつfflush()を呼び出す必要がありますか?

それぞれの最小限の例:

void RedirectStdout2File(const char* log_path) {
    int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
    dup2(fd,STDOUT_FILENO);
    if (fd != STDOUT_FILENO) close(fd);
}

int main_1(int argc, char* argv[]) {
    /* Case 1: stdout is line-buffered when run from console */
    printf("No redirect; printed immediately\n");
    sleep(10);
}

int main_2a(int argc, char* argv[]) {
    /* Case 2a: stdout is not line-buffered when redirected to file */
    RedirectStdout2File(argv[0]);
    printf("Will not go to file!\n");
    RedirectStdout2File("/dev/null");
}
int main_2b(int argc, char* argv[]) {
    /* Case 2b: flushing stdout does send output to file */
    RedirectStdout2File(argv[0]);
    printf("Will go to file if flushed\n");
    fflush(stdout);
    RedirectStdout2File("/dev/null");
}

int main_3(int argc, char* argv[]) {
    /* Case 3: printf before redirect; printf is line-buffered after */
    printf("Before redirect\n");
    RedirectStdout2File(argv[0]);
    printf("Does go to file!\n");
    RedirectStdout2File("/dev/null");
}
28
Patrick

バッファリングされた関数とバッファリングされていない関数を誤って組み合わせているIO関数。そのような組み合わせは、特にコードを移植可能にする必要がある場合に非常に注意深く行う必要があります。移植できないコードを記述するのは不適切です...)
確かに、バッファリングされたものとバッファリングされていないものを組み合わせないようにするのが最善ですIO同じファイル記述子上。

バッファIO:fprintf()fopen()fclose()freopen()...

Unbuffered IO:write()open()close()dup()...

dup2()を使用してstdoutをリダイレクトする場合。関数は、fprintf()によって満たされたバッファを認識​​していません。したがって、dup2()が古い記述子1を閉じると、バッファはフラッシュされず、コンテンツが別の出力にフラッシュされる可能性があります。あなたのケース2aでは、それは_/dev/null_に送信されました。

ソリューション

あなたの場合、freopen()の代わりにdup2()を使用するのが最善です。これはすべての問題を解決します:

  1. 元のFILEストリームのバッファーをフラッシュします。 (ケース2a)
  2. 新しく開いたファイルに従ってバッファリングモードを設定します。 (ケース3)

関数の正しい実装は次のとおりです。

_void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
_

残念ながら、バッファ付きIOは、新しく作成されたファイルの権限を直接設定することはできません。権限を変更するには、他の呼び出しを使用する必要があります。または、移植できないglibc拡張機能を使用できます。 fopen() man page

3
pabouk

ファイル記述子を閉じないでください。メッセージをファイルにのみ出力する場合は、close(fd)を削除してstdout_bak_fdを閉じます。

0
usain