【问题标题】:The consequences and pros/cons of flushing the stream in c++在 C++ 中刷新流的后果和优缺点
【发布时间】:2015-06-24 10:17:28
【问题描述】:

我最近阅读了一篇文章,其中指出使用\n 比使用std::endl 更可取,因为endl 也会刷新流。
但是当我寻找有关该主题的更多信息时,我发现了一个网站,上面写着:

如果您处于必须避免缓冲的情况,您可以使用 std::endl 代替 ‘\n’

现在我的问题来了:在哪种情况下最好写入缓冲区?因为我只看到了这种技术的优势。写入缓冲区不是也更安全吗?因为它比硬盘驱动器小,它会比存储在 HD 上的数据更快地被覆盖(我不确定这是否属实)。

【问题讨论】:

  • 使用缓冲时,您可能会执行一些对话框询问用户数据(带有提示),并且在您从键盘读取之前不会打印文本。在某些外围设备(例如串行端口)的情况下,您可能会写入缓冲区,并且当程序停止时,外围设备也会关闭(并且不会发送数据。

标签: c++ theory flush


【解决方案1】:

发生缓冲时,您无法保证在刷新发生之前立即接收到数据。在特定情况下,您可能会遇到错误的输出顺序和/或信息/调试数据丢失,例如

int main() {
   
    std::cout << "This text is quite nice and might as well be buffered";
    raise(SIGSEGV); // Oh dear.. segmentation violation
    std::cout << std::endl;
}

Live Example

输出:

bash: line 7: 22235 Segmentation fault      (core dumped) ./a.out

上述内容将打印任何文本,因为缓冲阻止了正确的输出显示。

现在,如果您只是在缓冲区末尾添加一个刷新std::endl,这就是您得到的

int main() {
   
    std::cout << "This text is quite nice and might as well be buffered" << std::endl;
    raise(SIGSEGV); // Oh dear.. segmentation violation
    std::cout << std::endl;
}

Live Example

输出:

This text is quite nice and might as well be buffered
bash: line 7: 22444 Segmentation fault      (core dumped) ./a.out

这一次输出是可见的之前程序终止。

这一事实的含义是多方面的。纯属推测:如果数据与服务器日志相关,那么您的应用可能在实际日志记录之前就崩溃了。

【讨论】:

    【解决方案2】:

    在您希望输出准确地出现在它应该出现的任何情况下,这将是更可取的。

    一个简单的例子:

    #include <iostream>
    int main() {
        std::cout << "Please enter your name: " << std::endl;
        std::string name;
        std::cin >> name;
        ...
    }
    

    使用缓冲,在用户输入他/她的名字之前,屏幕上不会出现任何文本,因此用户会感到困惑。 (请注意,实际上,在完全启用缓冲的情况下运行此示例可能非常困难或不可能,因为 C++ 可能会采取特殊措施在来自std::cin 的任何输入之前刷新std::cout,请参阅Why do we need to tie std::cin and std::cout?。但这只是一个理论上的例子:如果缓冲 完全启用,用户将看不到提示。)

    这种情况可能时有发生,但可能不会经常发生。考虑写入管道以与另一个进程交互。或者即使您的程序写入日志文件并且您不时亲自查看日志文件以查看它是如何运行的 --- 在缓冲的情况下,您通常不会看到已从程序打印的输出,但仍然还停留在缓冲区中。

    另一个需要说明的重要情况 --- 如果您的程序严重崩溃,缓冲区内容可能根本不会在硬盘驱动器上结束。 (我希望流析构函数刷新缓冲区,但崩溃可能非常严重,以至于根本不会调用析构函数。)

    【讨论】:

    • @Petr 但是你认为写入缓冲区比写入 HD 更安全吗?
    • @TheJavaFan,我不明白“覆盖”这个词。当缓冲区被填满时,它会被刷新(写入)到磁盘或流所指向的任何地方。在此之后,缓冲区被重用并且其中的旧数据确实被覆盖(尽管它已经在磁盘上),但我认为这与安全没有关系,也没有与 HD 类比,所以我不明白我们怎么能比较缓冲区和高清。
    • @TheJavaFan 如果您的意思是“缓冲会使事情变得更快”,答案是肯定的,这就是缓冲的全部目的。无论如何any time output timing is important你应该考虑利弊。这完全是特定于应用程序的。
    • @Petr 您的示例忽略了std::cinstd::cout 绑定的事实,因此std::cin 上的任何输入操作都会首先导致std::cout 的输出缓冲区被刷新。例如,请参阅stackoverflow.com/questions/14052627/…
    • 安德鲁完全正确——这个答案需要更新。 for (int i = 0; i &lt; 10; ++i) { std::cout &lt;&lt; "\rplease wait " &lt;&lt; i &lt;&lt; " more seconds..." &lt;&lt; std::flush; sleep(1); }.
    【解决方案3】:

    我希望您丢失了指向您找到的那个网站的链接。 std::endl 确实 避免缓冲。它刷新缓冲区中的任何内容。如果您需要避免缓冲,请使用setf(ios_base::unitbuf)。这会将流设置为在每次插入后刷新。这是std::clog 的默认设置。这样做的原因是缓冲区中保存的内容越少,当程序崩溃时,关键数据被写入流的机会就越大。

    刷新对于交互式程序也很重要:如果您向std::cout 写入提示,那么如果该提示在程序开始等待输入之前出现在显示屏上,那将是一件好事。当您使用 std::coutstd::cin 时,它会自动完成,除非您弄乱了同步设置。

    许多程序员似乎使用std::endl 作为'\n' 的一种奇特的拼写方式,但事实并非如此。您不需要在每次向其写入内容时刷新输出缓冲区。让操作系统和标准库做他们的工作;他们会负责及时将输出发送到适当的位置。一个简单的std::cout &lt;&lt; '\n'; 就是将换行符放入输出中所需的全部内容,并且迟早会显示在显示器上。如果您现在需要显示它,通常是因为您暂时已经编写了所有输出并且不想让显示的信息不完整,请在输出的最后一行之后使用std::endl

    【讨论】:

      【解决方案4】:

      首先,有点修正主义的历史。

      在过去,当每个人都使用 stdio.h 库进行 I/O 时,交互式查看的文本通常是行缓冲(甚至是无缓冲),以及未完全缓冲的文本。因此,如果您将'\n' 输出到流中,它会“始终”做正确的事情:用户正在查看的行会被刷新并立即看到,而被转储到文件的行会被缓冲以获得最佳性能。

      不幸的是,它实际上并不总是正确的。运行时不能总是预测用户实际希望如何查看程序的输出。一个常见的陷阱是重定向STDOUT——人们习惯于在控制台中运行他们的程序并在控制台中看到输出(具有行缓冲行为),然后出于任何原因(例如长时间运行的工作)他们决定将STDOUT 重定向到一个文件,并立即对输出不再是行缓冲的事实感到惊讶。

      我看到超级计算机为此浪费了数周的时间;输出频率太低,以至于缓冲使任何人都无法知道工作的进展情况。

      然而,C++ 的iostream 库旨在轻松 在这里做正确的事情。除了与stdio 同步时,它不会做这种有趣的“也许是行缓冲也许是完全缓冲”的事情。它总是使用完全缓冲(当然,当你做无缓冲的东西时除外),如果你想在换行符上刷新,你这样做显式

      因此,如果您要将一堆格式化文本转储到一个人们在完成之前不会查看的文件中,您可以写 \n 作为换行符。

      但是,如果您正在编写人们在编写时可能真正想要查看的文本,您可以使用std::endl 作为换行符,它会立即显示出来。如果你一次写几行,你甚至可以做得更好:使用'\n' 作为中间换行符,std::endl 作为最后一个换行符(或'\n'std::flush)。尽管在此设置中性能通常并不重要,但通常只使用 std::endl 来处理所有换行符就可以了。

      【讨论】:

      • 请注意:即使在 line-buffered 的情况下,输出也不会总是立即出现,它只会出现在第一个 new-线。如果您将所有内容输出到一行中,则输出将有效地被完全缓冲。要让输出真正立即显示,您需要在每次输出后显式调用flush()
      【解决方案5】:

      如果您需要流的目标在流关闭之前接收数据,最好刷新缓冲区。

      一个真实的例子是一个应用程序日志,它是从一个始终打开的流中写入的......您可能希望在程序仍在运行时查看此日志。

      【讨论】:

        猜你喜欢
        • 2010-10-30
        • 1970-01-01
        • 2012-04-01
        • 2010-09-20
        • 1970-01-01
        • 2011-01-06
        • 1970-01-01
        • 1970-01-01
        • 2011-01-27
        相关资源
        最近更新 更多