为什么printf在调用后不刷新,除非格式字符串中有换行符?

为什么printf在调用后不刷新,除非格式字符串中有换行符?这是POSIX行为吗?我如何让printf每次都立即刷新?

453161 次浏览

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

要获得立即输出,可以:

  1. 打印到标准错误。
  2. 使stdout无缓冲。

要立即刷新,请调用fflush(stdout)fflush(NULL) (NULL表示刷新所有内容)。

你可以fprintf到stderr,这是无缓冲的。或者您可以在需要时刷新stdout。或者你可以将stdout设置为unbuffered。

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

  • 打印到stderr而不是使用fprintf (stderr默认为未缓冲):

    fprintf(stderr, "I will be printed immediately");
    
  • 当你需要使用fflush时,Flush stdout:

    printf("Buffered, will be flushed");
    fflush(stdout); // Will now print everything in the stdout buffer
    
  • 使用setbuf禁用stdout的缓冲:

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

    setvbuf(stdout, NULL, _IONBF, 0);
    

这样做可能是为了提高效率,也因为如果有多个程序写入一个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

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

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

注意:微软运行时库不支持行缓冲,所以printf("will print immediately to terminal"):

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

不,这不是POSIX行为,而是ISO行为(好吧,它是 POSIX行为,但仅限于它们符合ISO)。

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

myprog >myfile.txt

这对于效率是有意义的,因为如果你与用户交互,他们可能想看到每一行。如果您将输出发送到一个文件,很可能另一端没有用户(尽管也不是不可能,他们可能在跟踪文件)。现在你认为可以用户想要看到每个字符,但这有两个问题。

首先是效率不高。其次,最初的ANSI C要求主要是编纂现有的行为,而不是发明行为,而且这些设计决策早在ANSI开始这个过程之前就已经做出了。现在,即使是ISO在改变标准中的现有规则时也非常谨慎。

至于如何处理这个问题,如果在您希望立即看到的每个输出调用之后使用fflush (stdout),就可以解决问题。

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

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

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

ISO C99 section 7.19.3/3是相关的位:

当流为无缓冲的时,字符将尽可能快地从源或目的地出现。否则,字符可能被累积起来,并作为一个块传输到主机环境或从主机环境传输出去。

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

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

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

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

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

一般有2级缓冲-

1. 内核缓冲区缓存(使读/写更快)

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

让我们以fprintf and write()为例。

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

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

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

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