35

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

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

何时进行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");
}
4

3 回答 3

41

Flushing forstdout由其缓冲行为决定。缓冲可以设置为三种模式:(完全缓冲:尽可能_IOFBF等待)、 (行缓冲:换行触发自动刷新)和(始终使用直接写入)。“对这些特性的支持是由实现定义的,并且可能会受到和函数的影响。” [C99:7.19.3.3]fflush()_IOLBF_IONBFsetbuf()setvbuf()

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

观察到的行为的解释

所以,发生的事情是实现做了一些特定于平台的事情来决定是否stdout要进行行缓冲。在大多数 libc 实现中,此测试在第一次使用流时完成。

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

一些实际的实现

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

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

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

于 2012-12-18T13:07:12.393 回答
4

您错误地组合了缓冲和非缓冲 IO 功能。这种组合必须非常小心地完成,尤其是当代码必须是可移植的时。(而且编写不可移植的代码是不好的......)
当然最好避免在同一个文件描述符上组合缓冲和非缓冲 IO。

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

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

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

解决方案

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

  1. 它刷新原始FILE流的缓冲区。(案例 2a)
  2. 它根据新打开的文件设置缓冲模式。(案例3)

这是您的功能的正确实现:

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

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

于 2013-08-23T14:47:23.823 回答
0

您不应该关闭文件描述符,因此如果您希望消息仅在文件中打印,请移除close(fd)并关闭。 stdout_bak_fd

于 2012-12-18T12:43:31.717 回答