标准 I/O 流 -- fgets() 缓冲类型
Standard I/O stream -- fgets() buffering type
书"advanced programming in unix environment"在第15章讨论了pipe,表明我们在处理标准I/O函数时应该注意缓冲类型。
不同开放标准 I/O 流的缓冲类型是(在本书第 5 章中讨论):
- 标准误差是
unbuffered
- 连接到终端设备的流是
line-buffered
- 所有其他流都是
fully-buffered
当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-buffered
或 unbuffered
:
另外,我也试过在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
系统调用,但代码使用 popen
。 popen
是一个不在 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对此保持沉默;它没有说任何关于缓冲的事情。它也没有说 fdopen
从 fopen
继承了任何特殊要求。它确实说模式标志的含义是 "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
也可以设置完全缓冲。
因此,如果您使用 fdopen
或 popen
,并且缓冲的选择在您的情况下很重要,您应该自己安排 setvbuf
.
关于:
I also tried to setvbuf() before fgets() ...
缓冲影响输出。 stdio
输入函数不会延迟将缓冲的输入数据传送到应用程序。您能够从连接到管道的进程中读取单独的行这一事实意味着该进程正在为每一行刷新自己的输出缓冲区。然后该行通过管道传输并可用于您自己的进程。 stdio
库不会延迟您的 fgets
操作,直到更多行累积,即使在完全缓冲的情况下也是如此。它不是这样工作的;完全缓冲意味着输出会累积,直到缓冲区填满或调用 fflush
。
书"advanced programming in unix environment"在第15章讨论了pipe,表明我们在处理标准I/O函数时应该注意缓冲类型。
不同开放标准 I/O 流的缓冲类型是(在本书第 5 章中讨论):
- 标准误差是
unbuffered
- 连接到终端设备的流是
line-buffered
- 所有其他流都是
fully-buffered
当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-buffered
或 unbuffered
:
另外,我也试过在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
系统调用,但代码使用 popen
。 popen
是一个不在 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对此保持沉默;它没有说任何关于缓冲的事情。它也没有说 fdopen
从 fopen
继承了任何特殊要求。它确实说模式标志的含义是 "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
也可以设置完全缓冲。
因此,如果您使用 fdopen
或 popen
,并且缓冲的选择在您的情况下很重要,您应该自己安排 setvbuf
.
关于:
I also tried to setvbuf() before fgets() ...
缓冲影响输出。 stdio
输入函数不会延迟将缓冲的输入数据传送到应用程序。您能够从连接到管道的进程中读取单独的行这一事实意味着该进程正在为每一行刷新自己的输出缓冲区。然后该行通过管道传输并可用于您自己的进程。 stdio
库不会延迟您的 fgets
操作,直到更多行累积,即使在完全缓冲的情况下也是如此。它不是这样工作的;完全缓冲意味着输出会累积,直到缓冲区填满或调用 fflush
。