2

在我的 C 程序中,在 Linux 上运行,它使用创建子进程system()我注意到,当我将stdout重定向到管道或文件时,子进程的输出在缓冲 I/O 函数的输出之前发送,例如printf(). 当标准输出留给终端时,输出按预期顺序排列。我将程序简化为以下示例:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("1. output from printf()\n");
    system("echo '2. output from a command called using system()'");

    return EXIT_SUCCESS;
}

stdout进入终端时的预期输出:

$ ./iobuffer
1. output from printf()
2. output from a command called using system()

将stdout重定向到管道或文件时输出乱序:

$ ./iobuffer | cat
2. output from a command called using system()
1. output from printf()
4

2 回答 2

5

终端通常使用行缓冲,而管道将使用块缓冲。

这意味着printf包含换行符的调用将填充行缓冲区,从而触发刷新。重定向时,在程序完成之前不会发生刷新。

echo另一方面,当完成时,总是刷新它正在写入的缓冲区。

使用行缓冲(终端输出),顺序为:

  • printf()用换行符打印一行,缓冲区被刷新,你看到1. output from printf()正在打印。
  • echo写入输出,退出,刷新缓冲区,你看到2. output from a command called using system()打印。

使用块缓冲,顺序为:

  • printf()用换行符打印一行,而不是完全填充缓冲区。
  • echo写入输出,退出,刷新缓冲区,你看到2. output from a command called using system()打印。
  • 您的程序退出,刷新其块缓冲区,您会看到1. output from printf()正在打印。

您的选项是使用显式刷新或使用显式设置fflush()缓冲。stdoutsetvbuf()

于 2013-07-03T09:40:52.843 回答
2

This reply is complementing the reply of Martijn Pieters. Citations of sources describing the default buffering modes of streams are at the end.

Solutions

Here are three basic solutions with explanation:

a) Flush the buffers before calling a sub-process. This option could be significantly more effective when you do a lot of output from the main program and just few sub-process calls.

printf("1. output from printf()\n");
fflush(stdout);
system("echo '2. output from a command called using system()'");

b) Change the buffering of stdout to line-buffering (or unbuffered) for the whole program. This option is a small change to the program as you call sevbuf() only at the beginning and the rest of the program stays the same.

if(setvbuf(stdin, NULL, _IOLBF, BUFSIZ))
    err(EXIT_FAILURE, NULL);
printf("1. output from printf()\n");
system("echo '2. output from a command called using system()'");

Edit:
c) Change the buffering of stdout to line-buffering (or unbuffered) for the whole program by an external utility. This option does not change the program at all so you do not need to recompile or even have sources of the program. You just call the program using the stdbuf utility.

$ stdbuf -oL ./iobuffer | cat
1. output from printf()
2. output from a command called using system()

References - describing why the buffering mode changes

The initial buffering setting is described for example in the documents below. Streams to interactive devices like terminal are by default line-buffered so that newline-ended messages appear immediately on the terminal. Pipes, files etc. use block-buffering (or full-buffering) for better performance.

The GNU C Library Reference Manual
http://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html#Buffering-Concepts

Newly opened streams are normally fully buffered, with one exception: a stream connected to an interactive device such as a terminal is initially line buffered.

Linux man-pages: stdin (3)
http://linux.die.net/man/3/stdin

The stream stderr is unbuffered. The stream stdout is line-buffered when it points to a terminal. Partial lines will not appear until fflush(3) or exit(3) is called, or a newline is printed. This can produce unexpected results, especially with debugging output. The buffering mode of the standard streams (or any other stream) can be changed using the setbuf(3) or setvbuf(3) call.

There are also a mention of the buffering of a terminal driver.

ISO/IEC 9899:201x C11 Committee Draft — April 12, 2011; 7.21.3 Files, page 301
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

As initially opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

The Open Group: System Interfaces and Headers Issue 4, Version 2; 2.4 Standard I/O Streams, page 32
https://www2.opengroup.org/ogsys/catalog/C435 (free registration needed for download)

When opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

There is also a very interesting chapter 2.4.1 "Interaction of File Descriptors and Standard I/O Streams" about combining of buffered and unbuffered I/O which somewhat relates to sub-process calls.

于 2013-07-04T14:21:11.860 回答