【问题标题】:Why does printf not flush after the call unless a newline is in the format string?除非换行符在格式字符串中,否则为什么 printf 在调用后不刷新?
【发布时间】:2023-02-10 19:27:58
【问题描述】:

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

【问题讨论】:

  • 您是否调查过这种情况是发生在任何文件上还是仅发生在终端上?这听起来是一个聪明的终端功能,不从后台程序输出未完成的行,但我希望它不适用于前台程序。
  • 在 Cygwin bash 下,即使是换行符,我也会看到同样的不当行为在格式字符串中。这个问题是 Windows 7 的新问题;相同的源代码在 Windows XP 上运行良好。 MS cmd.exe 按预期刷新。修复 setvbuf(stdout, (char*)NULL, _IONBF, 0) 解决了这个问题,但肯定没有必要。我正在使用 MSVC++ 2008 Express。 ~~~
  • 澄清问题的标题:printf(..)不做任何冲洗本身,它是 stdout 的缓冲,在看到换行符时可能会刷新(如果它是行缓冲的)。它会对putchar('\n'); 做出同样的反应,所以printf(..) 在这方面并不特殊。这与cout << endl;形成对比,documentation of which突出地提到了冲洗。 documentation of printf 根本没有提到冲洗。
  • 写入 (/flushing) 可能是一项昂贵的操作,可能出于性能原因对其进行了缓冲。
  • @EvgeniSergeev:是否一致认为问题诊断错误,并且当换行符时发生刷新输出? (将一个放在格式字符串中是一种方法,但不是在输出中获取一个的唯一方法)。

标签: c printf flush


【解决方案1】:

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

  • 打印到stderr而不是使用fprintfstderrunbuffered by default):

    fprintf(stderr, "I will be printed immediately");
    
  • 在需要时使用fflush刷新stdout

    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); 
    

【讨论】:

  • 或者,完全禁用缓冲:setbuf(stdout, NULL);
  • 另外,只是想提一下,显然在 UNIX 中,如果 stdout 是终端,换行符通常只会刷新缓冲区。如果输出被重定向到文件,则换行符不会刷新。
  • 我觉得我应该补充:我刚刚测试了这个理论,我发现在没有定向到终端的流上使用setlinebuf()在每行的末尾冲洗。
  • “最初打开时,标准错误流未完全缓冲;标准输入和标准输出流完全缓冲,当且仅当可以确定该流不引用交互式设备时”——参见这个问题:stackoverflow.com/questions/5229096/…
  • @RuddZwolinski 如果这将是“为什么不打印”的一个很好的经典答案,那么在这个高度赞成的答案中直接根据 "Does printf always flush the buffer on encountering a newline?" 提及终端/文件区别似乎很重要,而不是需要阅读 cmets 的人...
【解决方案2】:

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

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

myprog >myfile.txt

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

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

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

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

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

请记住,如果你将输出发送到文件。还要记住,对此的支持是实现定义的,标准不保证。

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

当流是无缓冲, 字符旨在尽快从源或目的地出现。否则,字符可能会作为一个块累积并传输到主机环境或从主机环境传输。

当流是完全缓冲, 字符旨在在缓冲区已满时作为块传输到主机环境或从主机环境传输。

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

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

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

【讨论】:

  • 我刚刚遇到一个场景,即使有一个 ' ', printf() 不刷新。正如您在此处提到的,通过添加 fflush(stdout) 克服了这一问题。但我想知道为什么 ' ' 未能刷新 printf() 中的缓冲区。
  • @QiangXu,只有在可以明确确定是指交互式设备的情况下,标准输出才会被行缓冲。因此,例如,如果您使用 myprog >/tmp/tmpfile 重定向输出,则该输出是完全缓冲的而不是行缓冲的。根据记忆,关于您的标准输出是否是交互式的决定留给实现。
  • 此外,在 Windows 上调用 setvbuf(...., _IOLBF) 将不起作用,因为 _IOLBF 与 _IOFBF 相同:msdn.microsoft.com/en-us/library/86cebhfs.aspx
【解决方案3】:

它可能是这样的,因为效率,因为如果你有多个程序写入一个 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

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

【讨论】:

  • 有趣的是,我认为这是唯一真正回答“为什么?”的答案。 - 应用适量的猜测。其他人解释它被缓冲了(对于通过使用术语“冲洗”表现出对它的意识的 OP 来说,这似乎不太必要)以及如何避免/控制它。不可否认,那里有足够的细节来提供有用的答案见解。但只有这一篇讨论了原因,并从这个角度回答了所有问题。干杯。
【解决方案4】:

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

【讨论】:

  • 请记住fflush(NULL);通常是一个非常糟糕的主意。如果您打开许多文件,它会降低性能,尤其是在多线程环境中,您将与所有东西争夺锁。
【解决方案5】:

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

要立即获得输出,可以:

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

【讨论】:

  • 或者fflush(stdout)
  • “因此只会在打印换行符后输出。”不仅如此,至少还有 4 个其他案例。缓冲区已满,写信给stderr(这个答案稍后提到),fflush(stdout)fflush(NULL)
  • 正如要点 2 所暗示的,“stdout 是缓冲的”是不正确的。默认情况下,stdout 是常规文件时是块缓冲的,而当它是 tty 时是行缓冲的。也许只需将“默认情况下”添加到短语“stdout is buffered”。
【解决方案6】:

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

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

【讨论】:

  • 在“正常”情况下,比printf 立即转到终端更糟糕的是,printffprintf 即使在立即使用其输出的情况下也会得到更粗略的缓冲。除非 MS 修复了问题,否则一个程序不可能从另一个程序捕获 stderr 和 stdout 并确定发送给每个程序的顺序。
  • 不,除非没有设置缓冲,否则它不会立即将其打印到终端。默认使用全缓冲
【解决方案7】:

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

【讨论】:

    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        【解决方案10】:

        通常有 2 个级别的缓冲 -

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

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

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

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

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

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

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

        【讨论】:

          猜你喜欢
          • 2010-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多