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

Pat*_*ick 28 c linux stdout

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

  1. 如果stdout发送到控制台,则printf()行缓冲并在打印换行符后刷新.
  2. 如果stdout被重定向到文件,则除非fflush()被调用,否则不刷新缓冲区.
  3. 此外,如果printf()stdout重定向到文件之前使用,则后续写入(对文件)是行缓冲的并在换行后刷新.

何时进行stdout行缓冲,什么时候fflush()需要调用?

最简单的例子:

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");
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*son 35

冲洗stdout是由其缓冲行为决定的.缓冲可以设置为三种模式:( _IOFBF完全缓冲:等待直到fflush()可能),_IOLBF(行缓冲:换行触发自动刷新)和_IONBF(直接写入总是使用)."对这些特性的支持是实现定义的,可能会受到影响setbuf()setvbuf()功能的影响." [C99:7.19.3.3]

"在程序启动时,预定义了三个文本流,无需明确打开 - 标准输入(用于读取传统输入),标准输出(用于写入常规输出)和标准错误(用于写入诊断输出).最初打开时,标准错误流未完全缓冲;当且仅当可以确定流不参考交互设备时,标准输入和标准输出流才完全缓冲.[C99:7.19.3.7]

观察行为的说明

因此,实现的是实现执行某些特定于平台的事情来决定是否stdout将进行行缓冲.在大多数libc实现中,此测试在首次使用流时完成.

  1. 行为#1很容易解释:当流用于交互式设备时,它是行缓冲的,并printf()自动刷新.
  2. 现在也预料到情况#2:当我们重定向到一个文件时,流被完全缓冲并且不会被刷新fflush(),除非你向它写入gobloads数据.
  3. 最后,对于仅对底层fd执行一次检查的实现,我们也理解案例#3.因为我们强制stdout的缓冲区在第一个中被初始化,所以printf()stdout获得了行缓冲模式.当我们换出fd转到文件时,它仍然是行缓冲的,因此数据会自动刷新.

一些实际的实现

每个libc都有自己解释这些要求的自由度,因为C99没有指定"交互设备"是什么,POSIX的stdio条目也没有扩展这个(超出要求stderr打开阅读).

  1. glibc的.请参见filedoalloc.c:L111.这里我们stat()用来测试fd是否为tty,并相应地设置缓冲模式.(这是从fileops.c调用的.)stdout最初有一个空缓冲区,它是在第一次使用流时根据fd 1的特性分配的.

  2. BSD libc.非常相似,但要遵循更清晰的代码!请参阅makebuf.c中的这一行

  • 好的,最后的答案!fsync,fstat,ftruncate等是一系列函数(在第2节中); 与fopen,fprintf,fflush完全无关(第3节).fsync在内核中,对你应用程序中的libc的stdio缓冲区一无所知. (3认同)
  • 我现在明白了.close是一个系统函数,它也不知道libc缓冲区. (2认同)