c++中flush the stream的后果和pros/cons

The consequences and pros/cons of flushing the stream in c++

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

If you are in a situation where you have to avoid buffering, you can use std::endl instead of ‘\n’

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

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

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

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

一个简单的例子:

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

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

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

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

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

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

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

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

首先,了解一下修正主义历史。

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

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

我已经看到数周的超级计算机时间因此而被浪费;输出很少见,以至于缓冲使任何人都无法知道工作的进展情况。

然而,

C++ 的 iostream 库旨在使容易 在这里做正确的事。除了与 stdio 同步时,它不会做这个有趣的 "maybe line-buffered maybe fully-buffered" 事情。它 总是 使用完全缓冲(当然,除非你做无缓冲的事情),如果你想在换行符上刷新,你这样做显式 .

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

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

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

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

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