【问题标题】:Why does printf not flush after the call unless a newline is in the format string?为什么 printf 在调用后不刷新,除非换行符在格式字符串中?
【发布时间】:2010-12-15 12:51:57
【问题描述】:

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

【问题讨论】:

  • 您是否调查过这种情况是发生在任何文件上还是仅在终端上?这听起来是一个聪明的终端功能,它不会从后台程序输出未完成的行,尽管我希望它不适用于 前台程序。
  • 在 Cygwin bash 下,即使换行符 is 在格式字符串中,我也会看到同样的错误行为。这个问题是 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 是缓冲的,所以只会在打印换行符后输出。

要立即获得输出,可以:

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

【讨论】:

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

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

【讨论】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

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

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

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

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

        【讨论】:

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

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

        【讨论】:

          【解决方案10】:

          一般有2级缓冲-

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

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

          我们以fprintf and write()为例。

          当您调用fprintf() 时,它不会直接写入文件。它首先进入程序内存中的 stdio 缓冲区。从那里通过使用 write 系统调用将其写入内核缓冲区缓存。所以跳过 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() 等其他函数来让内核在内核缓冲区中有数据时立即启动写入。

          【讨论】:

            猜你喜欢
            • 2023-02-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多