627

printf除非换行符在格式字符串中,否则为什么在调用后不刷新?这是 POSIX 行为吗?我怎么可能printf每次都立即冲水?

4

10 回答 10

821

默认情况下stdout,流是行缓冲的,因此只会在到达换行符后(或被告知时)显示缓冲区中的内容。您有几个选项可以立即打印:

  • 使用打印stderr代替fprintf(默认情况下stderr无缓冲的):

    fprintf(stderr, "I will be printed immediately");
    
  • 需要时冲洗stdout以使用fflush

    printf("Buffered, will be flushed");
    fflush(stdout); // Will now print everything in the stdout buffer
    
  • 使用以下命令禁用标准输出上的缓冲setbuf

    setbuf(stdout, NULL);
    
  • 或者使用更灵活的setvbuf

    setvbuf(stdout, NULL, _IONBF, 0); 
    
于 2009-11-11T17:04:46.113 回答
138

不,这不是 POSIX 行为,而是 ISO 行为(嗯,这POSIX 行为,但仅在它们符合 ISO 的范围内)。

如果可以检测到标准输出是指交互式设备,则标准输出是行缓冲的,否则它是完全缓冲的。所以有些情况下printf不会刷新,即使它得到一个换行符来发送,例如:

myprog >myfile.txt

这对提高效率很有意义,因为如果您正在与用户交互,他们可能希望看到每一行。如果您要将输出发送到文件,则很可能另一端没有用户(尽管并非不可能,但他们可能正在拖尾文件)。现在您可以争辩说用户希望看到每个字符,但这样做有两个问题。

首先是效率不高。第二个是最初的 ANSI C 要求主要是对现有行为进行编码,而不是发明新的行为,并且这些设计决策是在 ANSI 开始该过程之前很久就做出的。即使是如今的 ISO 在更改标准中的现有规则时也非常谨慎。

至于如何处理,如果您fflush (stdout)在每次输出调用之后都想立即看到,那将解决问题。

或者,您可以setvbuf在操作之前使用stdout, 将其设置为无缓冲,您不必担心将所有这些fflush行添加到您的代码中:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

请记住,如果您将输出发送到文件,可能会对性能产生相当大的影响。还要记住,对此的支持是实现定义的,标准不保证。

ISO C99 部分7.19.3/3是相关位:

当流未缓冲时,字符应尽快从源或目标出现。否则,字符可能会作为一个块累积并传输到主机环境或从主机环境传输。

当一个流被完全缓冲时,当缓冲区被填满时,字符将作为一个块传输到主机环境或从主机环境传输。

当流被行缓冲时,当遇到换行符时,字符将作为块传输到主机环境或从主机环境传输。

此外,当缓冲区被填满时,当在非缓冲流上请求输入时,或者当在需要从主机环境传输字符的行缓冲流上请求输入时,字符将作为块传输到主机环境.

对这些特性的支持是实现定义的,并且可能通过setbufsetvbuf函数受到影响。

于 2010-11-17T03:52:28.560 回答
31

这可能是因为效率的原因,并且因为如果您有多个程序写入单个 TTY,这样您就不会在一行上交错显示字符。因此,如果程序 A 和 B 正在输出,您通常会得到:

program A output
program B output
program B output
program A output
program B output

这很臭,但它比

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

请注意,它甚至不能保证在换行符上刷新,因此如果刷新对您很重要,您应该明确刷新。

于 2009-11-11T17:54:06.413 回答
30

立即刷新呼叫fflush(stdout)fflush(NULL)NULL意味着刷新所有内容)。

于 2009-11-11T16:26:05.647 回答
15

注意:Microsoft 运行时库不支持行缓冲,因此printf("will print immediately to terminal")

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf

于 2010-10-26T20:47:31.793 回答
13

stdout 是缓冲的,因此只会在打印换行符后输出。

要立即获得输出,请执行以下任一操作:

  1. 打印到标准错误。
  2. 使标准输出无缓冲。
于 2009-11-11T16:25:08.717 回答
11

默认情况下,stdout 是行缓冲的,stderr 是无缓冲的,文件是完全缓冲的。

于 2010-07-29T02:02:19.177 回答
10

您可以将 fprintf 转换为无缓冲的 stderr。或者,您可以在需要时刷新标准输出。或者您可以将标准输出设置为无缓冲。

于 2009-11-11T16:26:49.730 回答
10

用于setbuf(stdout, NULL);禁用缓冲。

于 2015-05-31T03:22:52.183 回答
6

通常有2个缓冲级别-

1. Kernel buffer Cache(使读/写更快)

2. I/O 库中的缓冲(减少系统调用次数)

让我们以fprintf and write().

当您调用时fprintf(),它不会直接写入文件。它首先进入程序内存中的 stdio 缓冲区。从那里通过使用 write 系统调用将其写入内核缓冲区缓存。所以跳过 I/O 缓冲区的一种方法是直接使用 write()。其他方法是使用setbuff(stream,NULL). 这将缓冲模式设置为无缓冲,数据直接写入内核缓冲区。为了强制将数据转移到内核缓冲区,我们可以使用“\n”,在默认缓冲模式“行缓冲”的情况下,将刷新 I/O 缓冲区。或者我们可以使用fflush(FILE *stream).

现在我们在内核缓冲区中。内核(/OS)希望最小化磁盘访问时间,因此它只读取/写入磁盘块。因此,当read()发出 a 时,这是一个系统调用,可以直接调用或通过 调用fscanf(),内核从磁盘读取磁盘块并将其存储在缓冲区中。之后,数据从这里复制到用户空间。

类似地,fprintf()从 I/O 缓冲区接收的数据由内核写入磁盘。这使得 read() write() 更快。

现在强制内核启动一个write(),之后数据传输由硬件控制器控制,还有一些方法。我们可以O_SYNC在写调用期间使用或类似的标志。或者我们可以使用其他函数,比如fsync(),fdatasync(),sync()让内核在内核缓冲区中有数据时立即启动写入。

于 2019-08-21T16:36:41.083 回答