在c++中刷新流的后果和优缺点

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

本文关键字:后果 优缺点 c++ 刷新      更新时间:2023-10-16

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

如果您必须避免缓冲,可以使用std::endl而不是'\n'

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

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

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

实时示例

输出:

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

实时示例

输出:

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

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

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

在任何情况下,如果您希望输出实际出现在它应该出现的时候,这都是最好的。

一个简单的例子:

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

有了缓冲,在用户输入姓名之前,屏幕上不会出现任何文本,因此用户会感到困惑。(请注意,事实上,在完全启用缓冲的情况下运行此示例可能非常困难或不可能,因为C++可能会采取特殊措施在std::cin的任何输入之前刷新std::cout,请参阅为什么我们需要将std::cin和std::cout联系起来?。但这只是一个理论示例:如果缓冲被完全启用,用户将看不到提示。)

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

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

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

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

首先,一点修正主义历史。

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

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

我看到超级计算机因为这个原因浪费了好几个星期的时间;输出的频率很低,以至于缓冲区使任何人都无法了解作业的进展情况。

然而,C++的iostream库的设计目的是让在这里做正确的事情变得简单。除了与stdio同步时,它不会做这种有趣的"可能是行缓冲的,可能是完全缓冲的"事情。它总是使用完全缓冲(当然,除非你做未缓冲的东西),如果你想在换行符上刷新,你可以显式地。

因此,如果你将一堆格式化的文本转储到一个文件中,而在完成之前人们不会看到它,那么你就为换行符编写n

但是,如果你在写文本,人们可能真的想在你写的时候看,你可以使用std::endl作为换行符,它会立即显示出来。如果你同时写几行,你甚至可以做得更好:使用'n'作为中间换行符,使用std::endl作为最后一行(或'n'std::flush)。尽管在这种设置中,性能通常无关紧要,所以通常只需对所有换行符使用std::endl即可。

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

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

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