为什么stdout会缓存?

14

我正在尝试学习libuv api,并编写了以下测试:

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

void timer_cb(uv_timer_t* timer) {
    int* i = timer->data;
    --*i;
    if(*i == 0) {
       uv_timer_stop(timer);
    }
    printf("timer %d\n", *i);
    //fflush(stdout);
}

int main() {
    uv_loop_t* loop = uv_default_loop();
    uv_timer_t* timer = malloc(sizeof(uv_timer_t));
    uv_timer_init(loop, timer);
    int i = 5;
    timer->data = &i;
    uv_timer_start(timer, timer_cb, 1000, 2000);

    uv_run(loop, UV_RUN_DEFAULT);

    printf("Now quitting.\n");
    uv_close(timer, 0);
    uv_loop_close(loop);

    return 0;
}

运行程序时,没有输出直到程序完成运行,然后所有的输出一次性显示出来。如果我取消注释fflush行,它按预期工作,每2秒写入一次。

有人能解释一下这是为什么吗?为什么stdout在换行符之后没有被刷新,正如这里和其他地方所解释的那样?为什么我需要手动刷新它?


2
为什么标准输出会缓冲?- 为什么不呢? - too honest for this site
@Olaf 我理解(请参见我链接的答案),缓冲区在换行时会刷新。 - Baruch
@LPs 我试过了,没有帮助。 - Baruch
你正在使用Eclipse吗?看看这个论坛 - LPs
有趣的是,它在gcc 10.2.0下以期望的方式工作,目标x86_64-pc-msys,Windows 10。可以从mintty和Windows Console启动。只有如果我将其管道化(例如到cat),它才会完全缓冲,这与管道不是tty的事实一致。您是否从显示输出的IDE中运行程序,该IDE使用不假装为控制台的小部件?(值得一提的是,使用Windows Sleep()构建为调试版本,在VS中运行并在控制台窗口中输出的等效程序也使用行缓冲,因此这显然不是Windows问题。) - Peter - Reinstate Monica
显示剩余5条评论
3个回答

18

流缓冲是由实现定义的。

根据C标准第7.21.3节第3段:

当流是非缓冲时,字符旨在尽快从源或目的地中显示。否则,字符可能会累积并作为块传输到或从主机环境中。当流是完全缓冲时,字符旨在在填充缓冲区时作为块传输到或从主机环境中。当流是行缓冲时,字符旨在在遇到换行符时作为块传输到或从主机环境中。此外,当需要从主机环境传输字符时,字符旨在作为块传输到主机环境,例如在请求非缓冲流上的输入时或请求需要从主机环境传输字符的行缓冲流上的输入时。这些特性的支持是实现定义的,并且可以通过setbuf和setvbuf函数进行更改。

缓冲类型取决于您的实现,而在您的示例中,您的实现显然不是行缓冲。


1
我使用 setvbuf(stdout, NULL, _IOLBF, 0) 将其设置为行缓冲,但仍然无法正常工作。只有当我将其设置为无缓冲 (_IONBF) 时才能正常工作。 - Baruch
1
正如答案中所链接的那样,在Windows上没有行缓冲。我认为这就是问题所在。 https://msdn.microsoft.com/zh-cn/library/86cebhfs.aspx#Anchor_3 - Baruch

7

没有强制要求stdout必须采用行缓冲。它也可以是完全缓冲(或者根本不缓冲),在这种情况下,\n不会触发刷新流。

C11 (N1570) 7.21.3/7 文件:

作为最初打开的状态,标准错误流并非完全缓冲;只有当可以确定流不指向交互式设备时,标准输入和标准输出流才是完全缓冲的。

C11 (N1570) 5.1.2.3/7 程序执行:

什么构成交互式设备是由实现定义的。

您可以尝试通过setvbuf标准函数来强制设置特定类型的缓冲。例如,要为stdout设置行缓冲,请尝试以下操作:

setvbuf(stdout, buff, _IOLBF, size);

在这里,buff 被声明为一个大小为 size 的字符数组(例如 1024)。

请注意,在执行流的任何其他 I/O 操作之前必须调用 setvbuf


我用 setvbuf(stdout, NULL, _IOLBF, 0) 将其设置为行缓冲,但仍然无法正常工作。只有当我将其设置为无缓冲(_IONBF)时才能正常工作。 - Baruch
@baruch:AFAIR,setvbuf需要提供自己准备的缓冲区才能正常工作。否则,它将无法工作。 - Grzegorz Szpetkowski

1
由于某种原因,您的系统认为您的标准输出不是交互式的。您是否对标准输出进行了奇怪的重定向或在终端上做了一些奇怪的操作?您应该能够使用setbuf覆盖,或者可以使用stderr代替stdout。

1
这并没有提供问题的答案。如果要对作者进行批评或请求澄清,请在他们的帖子下方留言。-【来自评论】 - StepUp
我认为它包括两个答案:使用setvbuf或stderr。第三个答案仅是暗示,需要找出为什么系统将stdout误认为是非交互式的。 - GroovyDotCom

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