新行字符是否也会清空缓冲区?

15
我知道像“endl\n的区别”这样的问题在SO上已经有很多答案了。但是它们只提到了endl能够将缓冲区刷新到stdout,而\n则不能。
因此,我理解的缓冲区被刷新是指输入被存储在一个缓冲区中,并且只有当它遇到endl或某些显式的flush函数时才传递到stdout。如果是这样,我预期以下代码:
#include <iostream>
#include <unistd.h>

int main(void)
{
    std::cout << "Hello\nworld";
    sleep(2);
    std::cout << std::endl;

    return 0;
}

展示:

2秒后

Hello
World

但实际输出为:

Hello

2秒后

World

为什么会这样呢?

不应该将\n也存储在缓冲区中,直到遇到endl时才将缓冲区刷新/显示到stdout吗?但是从我观察到的情况来看,\n的行为与endl相同。


8
这取决于 cout 的输出目标。如果输出到终端(即“交互设备”),那么它不能完全缓冲 - 通常是按行缓冲,这意味着在打印换行符后字符就会出现,或者在理论上可以是不带缓冲的。如果输出到管道、文件或其他非交互式目标,则 endl 强制将数据输出,即使流通常是完全缓冲的。 - Jonathan Leffler
我还想知道,如果我既没有提供换行符也没有使用 endl,当程序执行到结尾时,输出是否会显示在 stdout 上?我知道在终端上是这样的,但对于所有类型的 stdout 都适用吗? - CaptainDaVinci
2
是的,当文件流在程序(正常)结束时关闭时,挂起的输出将被刷新。当缓冲区满时,它也会被刷新。如果程序中止,挂起的输出通常不会被刷新。 - Jonathan Leffler
@JonathanLeffler:处理交互流的C++方式是将std::cin绑定到std::cout:每当访问std::cin时,std::cout就会被刷新。对于写入stdout的C++程序观察到行缓冲行为的实际原因是与stdio同步,显然,对于使用标准流的任何理智的C++程序,该同步已禁用,否则程序会变得不合理地减慢。 - Dietmar Kühl
2个回答

10

将注释转换为答案。

这取决于 cout 的输出目标。如果它输出到终端(“交互设备”),那么它不能完全缓冲——通常是行缓冲,也就是说字符会在打印出一个换行符后显示,或理论上可能是无缓冲的。如果它输出到管道、文件或其他非交互式目标,则 endl 即使流通常是完全缓冲的,也会强制输出数据。

我还想知道,如果我既不提供换行符也不提供 endl,当程序结束时,输出是否会显示在 stdout 上?我知道终端会,但对所有类型的 stdout 都适用吗?

是的,当文件流在程序正常结束时关闭时,等待输出将被刷新。当缓冲区满时,也会被刷新。如果程序异常终止,则等待输出通常不会被刷新。


7
标准C++流对象(std::cin,std::cout,std::cerr和std::clog)的默认设置是它们与相应的C流(stdin,stdout和stderr)同步。同步意味着交替访问C++和C流会产生一致的行为。例如,以下代码将输出字符串“hello, world”:
std::cout << "hel";
fprintf(stdout, "lo,");
std::cout << " wo";
fprintf(stdout, "rld");

C++标准并未强制规定如何实现同步。实现同步的一种方法是禁用std::cout(及其家族)的任何缓冲区,并立即访问stdout。也就是说,上述示例可以立即将各个字符写入stdout
如果字符实际上写入stdout,则stdout的缓冲模式默认设置将被使用。我在标准中找不到规范,但通常情况下,当stdout连接到交互流(例如控制台)时,stdout的缓冲模式默认为_IOLBF,即在每行末尾刷新缓冲区。写入文件的默认模式通常是_IOFBF,即当写入完整的缓冲区时刷新输出。因此,向std::cout写入换行符可能会导致刷新缓冲区。
C++中的流通常设置为带有缓冲区。也就是说,向文件写入换行符通常不会立即导致输出出现(除非该字符导致缓冲区溢出并且流设置为未缓冲)。由于与stdout的同步通常是不必要的,例如当程序始终使用std::cout来写入标准输出时,但这会使输出到标准输出的速度变得相当缓慢(禁用流的缓冲区使它们变得缓慢),因此可以禁用同步。
std::ios_base::sync_with_stdio(false);

这将禁用所有流对象的同步。对于不良实现可能没有效果,而良好的实现将启用std::cout的缓冲,从而显着提高速度,并可能还会禁用行缓冲。
一旦C++流被缓冲,就没有内置的方法让它在写入换行符时刷新。主要原因是处理行缓冲需要通过流缓冲区检查每个字符,这有效地抑制了字符的批量操作,从而导致显着的减速。如果需要,可以通过简单的过滤流缓冲器来实现行缓冲。例如:
class linebuf: public std::streambuf {
    std::streambuf* sbuf;
public:
    linebuf(std::streambuf* sbuf): sbuf(sbuf) {}
    int_type overflow(int_type c) {
        int rc = this->sbuf->sputc(c);
        this->sbuf->pubsync();
        return rc;
    }
    int sync() { return this->sbuf->pubsync(); }
};
// ...
int main() {
    std::ios_base::sync_with_stdio(false);
    linebuf sbuf(std::cout.rdbuf());
    std::streambuf* origcout = std::cout.rdbuf(&sbuf);

    std::cout << "line\nbuffered\n";

    std::cout.rdbuf(origcout); // needed for clean-up;
}

简而言之:C++标准没有行缓冲的概念,但当标准I/O从C的stdout同步时,可能会引入这一概念。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接