2

《unix环境中的高级编程》一书在第15章讨论了管道,它表明我们在处理标准I/O函数时应该注意缓冲类型。

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

  • 标准误是unbuffered
  • 连接到终端设备的流是line-buffered
  • 所有其他流是fully-buffered

当父/子连接到 a时,他们用来通信pipe的端(应该是类型对象,根据接口)应该根据上面的规则列表(因为它是连接到的流)。但是那一章中示例代码的行为似乎不是。FILE *fully-bufferedpipefully-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  }

所以我的问题是:fgets()在第 15 行popen1.c应该fully-buffered根据缓冲规则,为什么它的行为类似于line-bufferedunbuffered

此外,我之前也尝试setvbuf()fgets()专门将缓冲类型设置为_IOFBF(full-buffered) of fpin,还是不行。

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

2 回答 2

3

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

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

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

“缓冲规则”讨论了这种刷新何时自动发生。每次写入命令(fprintf、fputc 等)后,都会自动刷新未缓冲的流。每当将换行符写入流时,都会自动刷新行缓冲流。

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

于 2016-09-02T15:28:45.890 回答
0

您的代码与您的描述不符。您在谈论pipe系统调用,但代码使用popen. popen不是 ISO C 中而是 POSIX 中的函数,并且受 POSIX 中自己的一组要求的约束。不幸的是,POSIX 没有说明popen-ed 流的缓冲模式是什么。但是,它有这样奇怪的措辞:“打开输入过滤器之前的缓冲读取可能会使该过滤器的标准输入位置错误。输出过滤器的类似问题可以通过仔细的缓冲区刷新来防止;例如,使用 fflush。” 我无法理解第一句话:如何在打开之前进行阅读?第二句话似乎暗示popen流可能是完全缓冲的,因此很明确fflush可能需要确保将数据传递到输出管道。当然,如果进程本身正在使用完全缓冲读取输入,它可能无济于事!

如果您使用系统调用创建管道pipe,获得一对文件描述符,那么您可以FILE *使用fdopen. 这又不是 ISO C 函数。因此,它不受 ISO C 给出的要求的约束fopen,即:“打开时,当且仅当可以确定不引用交互式设备时,流是完全缓冲的。错误和文件结束指示符因为溪流被清除了。” 要查看这是否属实fdopen,我们必须研究 POSIX。不幸的是,POSIX 对此保持沉默。它没有说明缓冲。它也没有说fdopen继承任何特殊要求fopen。它确实说模式标志的含义是“与 中指定的完全相同fopen(),但以 w 开头的模式不会导致文件截断。”

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

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

关于:

我还尝试在 fgets() 之前 setvbuf() ...

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

于 2016-09-02T19:51:06.147 回答