Стандартный поток ввода / вывода - тип буферизации fgets()

Книга "Расширенное программирование в среде Unix" обсуждала канал в главе 15, она показывает, что мы должны обращать внимание на тип буферизации, когда имеем дело со стандартными функциями ввода-вывода.

Типы буферизации для различных открытых стандартных потоков ввода / вывода (обсуждаются в главе 5 книги):

  • стандартная ошибка unbuffered
  • потоки, подключенные к оконечным устройствам line-buffered
  • все остальные потоки fully-buffered

Когда родитель / ребенок подключается к 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  }

Итак, мой вопрос: fgets() в строке 15 popen1.c должно быть fully-buffered в соответствии с правилами буферизации, почему он действует как line-buffered или же unbuffered:

Кроме того, я также пытался setvbuf() до fgets() специально установить тип буферизации _IOFBF (полностью буферизованный) из fpin, до сих пор не работают.

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

2 ответа

Решение

В myuclc.c вы выполняете явный сброс для каждой новой строки:

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

Это заставляет поток быть буферизованным вручную. Всякий раз, когда вы очищаете канал, процесс на другом конце будет разблокирован и будет читать то, что было в буфере в то время.

"Правила буферизации" говорят о том, когда этот сброс происходит автоматически. Небуферизованные потоки автоматически сбрасываются после каждой команды записи (fprintf, fputc и т. Д.). Строковые буферизованные потоки автоматически сбрасываются всякий раз, когда в поток записывается символ новой строки.

И все потоки сбрасываются, когда буфер заполняется, когда поток закрывается или когда пишущий выполняет явную очистку

Ваш код не соответствует вашему описанию. Вы говорите о pipe системный вызов, но код использует popen, popen это функция не в ISO C, а в POSIX, и к ней применяется собственный набор требований в POSIX. POSIX не говорит, что такое режим буферизации popen потоки, к сожалению. Однако он имеет такую ​​любопытную формулировку: "Буферизованное чтение перед открытием входного фильтра может оставить неправильным расположение стандартного ввода этого фильтра. Подобные проблемы с выходным фильтром могут быть предотвращены путем тщательной очистки буфера, например, с помощью 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.

Таким образом, если вы используете fdopen или же popen и выбор буферизации имеет значение в вашей ситуации, вы должны сами setvbuf ,

Что касается:

Я также пытался setvbuf() перед fgets() ...

Буферизация влияет на вывод. stdio Функции ввода не задерживают доставку буферизованных входных данных в приложение. Тот факт, что вы можете читать отдельные строки из процесса, подключенного к каналу, означает, что этот процесс очищает свой собственный выходной буфер для каждой строки. Эта линия затем передается по каналу и доступна для вашего собственного процесса. stdio библиотека не собирается задерживать ваш fgets работа, пока не накопится больше строк, даже при полной буферизации. Это не так, как это работает; полная буферизация означает, что вывод накапливается до тех пор, пока буфер не заполнится или fflush называется.

Другие вопросы по тегам