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


538

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


2
您是否调查了是否使用任何文件还是仅使用终端发生了这种情况?这听起来是一个聪明的终端功能不是从后台程序的输出未完成的线,但我希望它不会应用到前台程序。
PypeBros

7
在Cygwin的bash的我看到即使换行此相同的不当行为在格式字符串。此问题是Windows 7的新问题;相同的源代码在Windows XP上运行良好。MS cmd.exe按预期刷新。该修复程序setvbuf(stdout, (char*)NULL, _IONBF, 0)可以解决该问题,但是肯定没有必要。我正在使用MSVC ++ 2008 Express。~~~
史蒂夫·投手

9
为了澄清问题的标题:printf(..) 本身不会进行任何刷新,这是stdout看到换行符时(如果有行缓冲)可能会刷新的缓冲。它对的反应方式相同putchar('\n');,因此printf(..)在这方面并不特殊。与此相反cout << endl;,该文件的文档主要提到冲洗。printf文档根本没有提到冲洗。
Evgeni Sergeev

1
写入(/刷新)可能是一项昂贵的操作,可能出于性能原因而对其进行了缓冲。
hanshenrik

@EvgeniSergeev:是否已达成共识,即该问题未正确诊断问题,并且在输出中存在换行符时会发生刷新?(在格式字符串中输入一个是在输出中获取一个的一种方法,但不是唯一的方法)。
Ben Voigt,

Answers:


701

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

打印到,stderr而不是使用fprintf默认情况下stderrunbuffered):

fprintf(stderr, "I will be printed immediately");

在需要使用时冲洗标准输出fflush

printf("Buffered, will be flushed");
fflush(stdout); // Will now print everything in the stdout buffer

编辑:从下面的Andy Ross的评论中,您还可以使用setbuf以下命令在stdout上禁用缓冲:

setbuf(stdout, NULL);

266
或者,要完全禁用缓冲:setbuf(stdout, NULL);
Andy Ross

80
另外,只想提一下,显然在UNIX中,如果stdout是终端,换行符通常只会刷新缓冲区。如果将输出重定向到文件,则不会刷新换行符。
霍拉

5
我觉得我应该补充:我刚刚一直在测试这个理论,我发现,使用setlinebuf()其上并不直接给终端流在每行的最后冲洗。
Doddy

8
“最初打开时,标准错误流没有完全缓冲;只有当可以确定该流不引用交互式设备时,标准输入和标准输出流才被完全缓冲” –请参阅以下问题:stackoverflow.com / questions / 5229096 /…
Seppo Enarvi 2015年

3
@RuddZwolinski如果这将是“为什么不打印”的一个很好的标准答案,则似乎很重要的一点是要提及终端/文件的区别,如“ printf是否总是在遇到换行符时刷新缓冲区?” 直接在这个高度评价的答案中,需要人们阅读评论...
HostileFork说不要相信SE SE

128

不,这不是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功能的影响。


8
我只是遇到了即使有一个'\ n',printf()也不会刷新的情况。如您在此处提到的,通过添加fflush(stdout)克服了这一问题。但是我想知道为什么'\ n'无法刷新printf()中的缓冲区的原因。
徐强

11
@QiangXu,仅在可以确定将其确定为引用交互设备的情况下,才对标准输出进行行缓冲。因此,例如,如果使用重定向输出myprog >/tmp/tmpfile,则该输出将完全缓冲,而不是行缓冲。从内存中,有关标准输出是否是交互式的确定将留给实现。
paxdiablo 2012年

3
此外,在Windows上调用setvbuf(....,
IOLBF

28

可能是因为效率高,并且因为如果您有多个程序写入单个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

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


26

立即清除通话fflush(stdout)fflush(NULL)NULL表示清除所有内容)。


31
记住fflush(NULL);通常是一个非常糟糕的主意。如果打开了许多文件,这会降低性能,尤其是在多线程环境中,其中您将为锁而战。
R .. GitHub停止帮助ICE,

14

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

https://docs.microsoft.com/zh-cn/cpp/c-runtime-library/reference/setvbuf


3
差于printf立即在“正常”情况下,终端会是事实,printffprintf得到更粗在它们的输出被立即采用情况下甚至缓冲。除非MS修复了问题,否则一个程序就不可能从另一个程序捕获stderr和stdout并确定将消息按什么顺序发送给每个程序。
2015年

不,除非没有设置缓冲,否则它不会立即将其打印到终端。默认情况下,使用完全缓冲
phuclv

12

stdout已缓冲,因此仅在换行符输出后输出。

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

  1. 打印到stderr。
  2. 使stdout不受缓冲。

10
或者fflush(stdout)
RastaJedi '16

2
“所以只有在换行符打印后才输出。” 不仅如此,而且至少还有其他4种情况。缓冲区已满,写入stderr(此答案稍后会提到)fflush(stdout),,fflush(NULL)
chux-恢复莫妮卡

11

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




2

通常有2级缓冲-

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

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

让我们以为例fprintf and write()

调用时fprintf(),它不会直接写入文件。它首先进入程序内存中的stdio缓冲区。使用写入系统调用从那里将其写入内核缓冲区高速缓存。因此,跳过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(),一旦内核缓冲区中有数据,就使内核启动写操作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.