C 为什么在重定向到文件时stdout需要显式刷新?

C 为什么在重定向到文件时stdout需要显式刷新?,c,linux,stdout,C,Linux,Stdout,printf()的行为似乎取决于stdout的位置 如果stdout被发送到控制台,则printf()将被行缓冲,并在打印换行后刷新 如果将stdout重定向到文件,则除非调用fflush(),否则不会刷新缓冲区 此外,如果在将stdout重定向到文件之前使用printf(),则后续写入(到文件)将被行缓冲,并在换行后刷新 何时缓冲stdout行,何时需要调用fflush() 每种方法的最小示例: 您不应该关闭文件描述符,因此请删除close(fd)并关闭 如果希望仅在文件中打印消息,则stdo

printf()
的行为似乎取决于
stdout
的位置

  • 如果
    stdout
    被发送到控制台,则
    printf()
    将被行缓冲,并在打印换行后刷新
  • 如果将
    stdout
    重定向到文件,则除非调用
    fflush()
    ,否则不会刷新缓冲区
  • 此外,如果在将
    stdout
    重定向到文件之前使用
    printf()
    ,则后续写入(到文件)将被行缓冲,并在换行后刷新
  • 何时缓冲
    stdout
    行,何时需要调用
    fflush()

    每种方法的最小示例:
    您不应该关闭文件描述符,因此请删除
    close(fd)
    并关闭
    
    
    如果希望仅在文件中打印消息,则stdout_bak_fd

    stdout的刷新取决于其缓冲行为。缓冲可以设置为三种模式:
    \u IOFBF
    (完全缓冲:如果可能,等待到
    fflush()
    )、
    \u IOLBF
    (行缓冲:换行触发自动刷新)和
    \u IONBF
    (始终使用直接写入)。“对这些特性的支持由实现定义,可能通过
    setbuf()
    setvbuf()
    函数受到影响。”[C99:7.19.3.3]

    “在程序启动时,预先定义了三个文本流,无需显式打开 -标准输入(用于读取常规输入),标准输出(用于写入 常规输出)和标准错误(用于写入诊断输出)。与最初一样 打开时,标准错误流未完全缓冲;标准输入和标准 当且仅当可以确定流不引用时,输出流才被完全缓冲 连接到交互式设备。“[C99:7.19.3.7]

    对观察到的行为的解释 因此,所发生的是,实现执行了特定于平台的操作,以决定是否对
    stdout
    进行行缓冲。在大多数libc实现中,此测试是在首次使用流时完成的

  • 行为#1很容易解释:当流用于交互设备时,它是行缓冲的,并且
    printf()
    会自动刷新
  • 案例2现在也在意料之中:当我们重定向到一个文件时,该流被完全缓冲,除非使用
    fflush()
    ,否则不会被刷新,除非您向其写入大量数据
  • 最后,对于只对底层fd执行一次检查的实现,我们也了解案例3。因为我们强制在第一个
    printf()
    中初始化stdout的缓冲区,所以stdout获得了行缓冲模式。当我们交换fd以转到文件时,它仍然是行缓冲的,因此数据会自动刷新
  • 一些实际的实现 每个libc在解释这些需求时都有一定的自由度,因为C99没有指定什么是“交互式设备”,也没有扩展这一点(除了要求stderr开放阅读)

  • 格里伯。看见这里我们使用
    stat()
    测试fd是否为tty,并相应地设置缓冲模式。(从fileops.c调用)
    stdout
    最初有一个空缓冲区,它在第一次使用流时根据fd1的特性分配

  • BSD libc。非常相似,但要遵循更干净的代码!看


  • 您错误地组合了缓冲和非缓冲IO函数。这样的组合必须非常小心,尤其是当代码必须是可移植的时。(而且写不可移植的代码是不好的…)
    最好避免在同一个文件描述符上组合缓冲和非缓冲IO。

    缓冲IO:
    fprintf()
    fopen()
    fclose()
    freopen()

    无缓冲IO:
    write()
    open()
    close()
    dup()

    使用
    dup2()
    重定向标准输出时。函数不知道由
    fprintf()
    填充的缓冲区。因此,当
    dup2()
    关闭旧描述符1时,它不会刷新缓冲区,内容可能会刷新到不同的输出。在您的案例2a中,它被发送到
    /dev/null

    解决方案 在您的情况下,最好使用
    freopen()
    而不是
    dup2()
    。这解决了您的所有问题:

  • 它刷新原始
    文件
    流的缓冲区。(案例2a)
  • 它根据新打开的文件设置缓冲模式。(案例3)
  • 以下是您的功能的正确实现:


    不幸的是,对于缓冲IO,您无法直接设置新创建文件的权限。您必须使用其他调用来更改权限,或者您可以使用不可移植的glibc扩展。请参阅。

    您应该检查所有系统调用的错误。@Joachim感谢您的建议。我只是删除了这里的检查代码,使代码更具可读性。错误检查不能解决我的问题。所以我想没有一个函数会返回错误?谢谢你关于错误检查和漏洞修复的建议。但我还是不明白我为什么要在这里冲刺。为什么在重定向之前打印一些消息可以解决这个问题。@Patrick啊,我明白了。在这种情况下,这个问题的傻瓜:谢谢你的回答。但是我的代码中有“\n”,它应该触发刷新吗?好的,最后一个答案!fsync、fstat、ftruncate等是一系列功能(在man第2节中);与fopen、fprintf、fflush(第3节)完全无关。fsync在内核中,对应用程序中libc的stdio缓冲区一无所知。close是一个系统函数,它也不知道libc缓冲区。
    void RedirectStdout2File(const char* log_path) {
        int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
        dup2(fd,STDOUT_FILENO);
        if (fd != STDOUT_FILENO) close(fd);
    }
    
    int main_1(int argc, char* argv[]) {
        /* Case 1: stdout is line-buffered when run from console */
        printf("No redirect; printed immediately\n");
        sleep(10);
    }
    
    int main_2a(int argc, char* argv[]) {
        /* Case 2a: stdout is not line-buffered when redirected to file */
        RedirectStdout2File(argv[0]);
        printf("Will not go to file!\n");
        RedirectStdout2File("/dev/null");
    }
    int main_2b(int argc, char* argv[]) {
        /* Case 2b: flushing stdout does send output to file */
        RedirectStdout2File(argv[0]);
        printf("Will go to file if flushed\n");
        fflush(stdout);
        RedirectStdout2File("/dev/null");
    }
    
    int main_3(int argc, char* argv[]) {
        /* Case 3: printf before redirect; printf is line-buffered after */
        printf("Before redirect\n");
        RedirectStdout2File(argv[0]);
        printf("Does go to file!\n");
        RedirectStdout2File("/dev/null");
    }
    
    void RedirectStdout2File(const char* log_path) {
        if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
    }