标准 I/O 流 -- fgets() 缓冲类型

Standard I/O stream -- fgets() buffering type

书"advanced programming in unix environment"在第15章讨论了pipe,表明我们在处理标准I/O函数时应该注意缓冲类型。

不同开放标准 I/O 流的缓冲类型是(在本书第 5 章中讨论):

当parent/child连接到pipe时,他们用来通信的端点(根据接口应该是FILE *类型的对象)应该是fully-buffered 根据上面的规则列表(因为它是连接到 pipe 的流)。但是该章示例代码的行为似乎不是 fully-buffered.

示例代码如下:

myuclc.c:

1   #include "apue.h"
2   #include <ctype.h>

3   int
4   main(void)
5   {
6       int     c;

7       while ((c = getchar()) != EOF) {
8           if (isupper(c))
9               c = tolower(c);
10          if (putchar(c) == EOF)
11              err_sys("output error");
12          if (c == '\n')
13              fflush(stdout);
14      }
15      exit(0);
16  }

popen1.c:

1   #include "apue.h"
2   #include <sys/wait.h>

3   int
4   main(void)
5   {
6       char    line[MAXLINE];
7       FILE    *fpin;
8
9       if ((fpin = popen("myuclc", "r")) == NULL)  // "myuclc" is executable file compile-link by "myuclc.c"
10          err_sys("popen error");
11      for ( ; ; ) {
12          fputs("prompt> ", stdout);
13          fflush(stdout);
14
15          if (fgets(line, MAXLINE, fpin) == NULL) /* read from pipe */
16              break;
17          if (fputs(line, stdout) == EOF)
18              err_sys("fputs error to pipe");
19      }
20      if (pclose(fpin) == -1)
21          err_sys("pclose error");
22      putchar('\n');
23      exit(0);
24  }

所以我的问题是:根据缓冲规则,popen1.c 的第 15 行中的 fgets() 应该是 fully-buffered,为什么它表现得像 line-bufferedunbuffered:

另外,我也试过在fgets()之前setvbuf()专门设置缓冲类型为fpin_IOFBF(全缓冲),还是不行。

prompt> abc
abc
prompt> ABC
abc
prompt> efg
efg
prompt> EFG
efg
prompt>

在 myuclc.c 中,您对每个换行符执行显式刷新:

12          if (c == '\n')
13              fflush(stdout);

这会导致流被手动行缓冲。每当您刷新管道时,另一端的进程将被解除阻塞并读取当时缓冲区中的任何内容。

"buffering rules" 讨论何时自动刷新。每次写入命令(fprintf、fputc 等)后,无缓冲流都会自动刷新。每当向流中写入换行符时,行缓冲流都会自动刷新。

当缓冲区填满、关闭流或编写器执行显式刷新时,所有流都会被刷新

您的代码与您的描述不符。你说的是 pipe 系统调用,但代码使用 popenpopen 是一个不在 ISO C 中但在 POSIX 中的函数,并且在 POSIX 中受制于它自己的一组要求。不幸的是,POSIX 没有说明 popen-ed 流的缓冲模式是什么。不过,它有这样奇怪的措辞: "Buffered reading before opening an input filter may leave the standard input of that filter mispositioned. Similar problems with an output filter may be prevented by careful buffer flushing; for example, with fflush." 我无法理解第一句话:阅读如何在打开之前发生?第二句话似乎暗示 popen 流可能被完全缓冲,因此可能需要显式 fflush 以确保数据传递到输出管道。当然,如果 那个 进程本身正在使用全缓冲读取输入,它可能没有帮助!

如果您使用 pipe 系统调用创建管道,获得一对文件描述符,然后您可以使用 fdopen 在这些描述符上创建 FILE * 流。同样,这不是 ISO C 函数。因此,它不受 ISO C 对 fopen 的要求的约束,即:"When opened, a stream is fully buffered if and only if it can be determined not to refer to an interactive device. The error and end-of-file indicators for the stream are cleared." 要查看 fdopen 是否如此,我们必须调查 POSIX。不幸的是,POSIX对此保持沉默;它没有说任何关于缓冲的事情。它也没有说 fdopenfopen 继承了任何特殊要求。它确实说模式标志的含义是 "exactly as specified in fopen(), except that modes beginning with w shall not cause truncation of the file."

POSIX 有 fopen 的描述,并且该描述逐字反映了上面引用的关于缓冲的 ISO C 文本。由于 POSIX 对 fdopen 的描述没有任何此类文本,也没有任何要求 fdopen 必须遵循 fopen 的要求(除了关于模式标志的含义),由 fdopen 设置的缓冲悬而未决。即使文件描述符是 TTY,符合规范的 fdopen 也可以设置完全缓冲。

因此,如果您使用 fdopenpopen,并且缓冲的选择在您的情况下很重要,您应该自己安排 setvbuf .

关于:

I also tried to setvbuf() before fgets() ...

缓冲影响输出stdio 输入函数不会延迟将缓冲的输入数据传送到应用程序。您能够从连接到管道的进程中读取单独的行这一事实意味着该进程正在为每一行刷新自己的输出缓冲区。然后该行通过管道传输并可用于您自己的进程。 stdio 库不会延迟您的 fgets 操作,直到更多行累积,即使在完全缓冲的情况下也是如此。它不是这样工作的;完全缓冲意味着输出会累积,直到缓冲区填满或调用 fflush