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

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

  1. 如果stdout被发送到控制台,则printf()是行缓冲的,并在打印换行符后被刷新。
  2. 如果stdout被redirect到一个文件,除非fflush()被调用,否则缓冲区不会被刷新。
  3. 而且,如果在将stdoutredirect到文件之前使用printf() ,则后续写入(对该文件)将被行caching,并在换行之后进行刷新。

什么时候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"); } 

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

“在程序启动时,三个文本stream是预定义的,不需要明确打开 – 标准input(用于读取常规input),标准输出(用于写入常规输出)和标准错误(用于写入诊断输出)。标准错误stream没有被完全缓冲;标准input和标准输出stream被完全缓冲,当且仅当stream可以被确定为不涉及交互设备时。 [C99:7.19.3.7]

观察行为的解释

那么,会发生什么呢是这个实现做了一些特定于平台的事情来决定stdout是否将被行缓冲。 在大多数libc实现中,这个testing在stream首次使用时完成。

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

一些实际的实现

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

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

  2. BSD libc。 非常相似,但更简洁的代码要遵循! 在makebuf.c中看到这一行

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

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

Unbuffered IO: write()open()close()dup()

当你使用dup2()来redirect标准输出。 该函数不知道由fprintf()填充的缓冲区。 所以当dup2()closures旧描述符1时,它不会刷新缓冲区,并且内容可能被刷新到不同的输出。 在你的情况2a它发送到/dev/null

解决scheme

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

  1. 它刷新原始FILEstream的缓冲区。 (情况2a)
  2. 它根据新打开的文件设置缓冲模式。 (情况3)

这里是你的函数的正确实现:

 void RedirectStdout2File(const char* log_path) { if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL); } 

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

您不应该closures文件描述符,所以如果您希望只在文件中打印消息,请移除close(fd)并closuresstdout_bak_fd