在 C 中,完全缓冲的输出流是否可以在缓冲区完全填满之前自动刷新?

Can a output stream, in C, which is full buffered, be flushed automatically even before the buffer is fully filled?

考虑这段代码:

#include <stdio.h>
int main()
{
    char buffer[500];
    int n = setvbuf(stdout, buffer, _IOFBF, 100);
    
    printf("Hello");
    while(1 == 1)
        ;
    return 0;
}

当 Linux 上的 运行 时,“Hello”消息立即出现在输出设备上,然后程序无限期挂起。在 stdout 被刷新或关闭之前,是否应该手动或在正常程序终止时缓冲输出?这似乎是 Windows 10 上发生的情况,如果缓冲区大小指定为 130 字节或更多,Linux 上也会发生这种情况。我在两个系统上都使用 VS Code。

我错过了什么?我对全缓冲概念的理解有误吗?

鉴于标准中缺乏明确性,我认为这种行为并不被禁止。

根据https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05

当流“无缓冲”时,字节会尽快从源或目的地出现;否则,字节可能被累积并作为一个块传输。当一个流被“完全缓冲”时,字节将在缓冲区被填满时作为一个块进行传输。当流是“行缓冲”时,字节旨在在遇到字节时作为块传输。此外,当缓冲区已满、在无缓冲流上请求输入时或在需要传输字节的行缓冲流上请求输入时,字节旨在作为块传输。对这些特性的支持是实现定义的,可能会受到 setbuf() 和 setvbuf() 的影响。

What am I missing? Am I wrong about the Full Buffering Concept?

你的概念没有错。正如@WilliamPursell 在 中观察到的那样,规范语言的措辞存在回旋余地,但根据规范的明确意图,您的程序观察到的行为并未表现出完全缓冲。此外,我将规范解释为尽管由于某种原因无法实现意图,但在这里为实现留出了符合的空间,而不是为能够合理实现意图的实现提供免费通行证,但可以随意做一些不同的事情。

我在 Linux:

上针对 Glibc 2.22 测试了你程序的这个变体
#include <stdio.h>

int main() {
    static char buffer[BUFSIZ] = { 0 };
    int n = setvbuf(stdout, buffer, _IOFBF, 100);

    if (n != 0) {
        perror("setvbuf");
        return 1;
    }

    printf("Hello");
    puts(buffer);
    return 0;
}

程序以状态0退出,没有打印任何错误输出,所以我断定setvbuf returned 0,说明成功。然而,程序只打印了一次“Hello”,表明实际上它并没有使用指定的缓冲区。如果我将指定给 setvbuf 的缓冲区大小增加到 128 字节 (== 27),则输出为“HelloHello”,表明已使用指定的缓冲区。

然后,观察到的行为似乎是1 setvbuf 的这种实现默默地将流设置为 un当指定提供的缓冲区小于 128 字节时缓冲。这也与您的程序版本的行为一致,但与我对函数规范的阅读不一致:

[...]The argument mode determines how stream will be buffered, as follows: _IOFBF causes input/output to be fully buffered [...]. If buf is not a null pointer, the array it points to may be used instead of a buffer allocated by the setvbuf function and the argument size specifies the size of the array; otherwise, size may determine the size of a buffer allocated by the setvbuf function. The contents of the array at any time are indeterminate.

The setvbuf function returns zero on success, or nonzero if an invalid value is given for mode or if the request cannot be honored.

(C17, 7.21.5.6/2-3)

在我阅读规范时,setvbuf 可以自行决定是否使用指定的缓冲区,如果它选择不这样做,那么它可能会或可能不会使用指定的缓冲区大小,但它必须设置指定的缓冲模式或失败。将缓冲模式更改为不同于原始模式和请求模式的缓冲模式与那些规范不一致,并且未能设置请求模式并且仍然return 0.

鉴于我断定此 Glibc 版本的 setvbuf 行为违反了语言规范,我想说你被 glibc 错误绊倒了。


1 但是需要注意的是,规范上说任何时候buffer的内容都是不确定的。因此,通过在请求 setvbuf 将其分配为流缓冲区后访问缓冲区,该程序会调用未定义的行为,因此,从技术上讲,它不能证明任何事情。