puts() не очищает буфер в программе перенаправления ввода-вывода

код следующим образом:

int main(int argc, char **argv)
{
   const char *file = "/tmp/out"; 
   int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
   int stdout_tmp = dup(1);
   close(1);
   dup(fd);
   puts("hello world!");
   // fflush(stdout);
   close(1);
   close(fd);
   dup(stdout_tmp);
   puts("redirect completed!");
   exit(0);
}

Я успешно скомпилировал код без каких-либо предупреждений, используя gcc10.2.0. Вопреки моему ожиданию, обе строки выводятся в стандартный вывод, а не в файл hello world в файле /tmp/out, и перенаправление завершено! в стандартный вывод. Когда раскомментируйте fflush(stdout), он работает!

Я предполагаю, что puts() не обновляет буфер в пользовательском пространстве, после восстановления стандартного вывода и выхода буфер автоматически обновляется.

получает () выходную строку с завершающим '\n', и буфер stdout будет автоматически обновляться при встрече с '\n'. Зачем нужно вызывать fflush(stdout) вручную?


person Harrison Lee    schedule 04.09.2020    source источник


Ответы (2)


Файл stdout (где пишет puts) буферизуется строкой (т. е. сбрасывает буфер на новой строке) только при подключении к терминалу (в основном, когда isatty(fileno(stdout)) истинно).

При подключении к другому нетерминальному выходу он полностью буферизуется, что означает, что вам нужно либо полностью заполнить буфер, либо вызвать fflush явно для очистки буфера.

person Some programmer dude    schedule 04.09.2020
comment
В качестве альтернативы вы можете настроить новый файловый дескриптор на линейную буферизацию с помощью setvbuf(). Он принимает параметр FILE *, который можно получить из файлового дескриптора с помощью fdopen(). - person Roberto Caboni; 04.09.2020

man 3 setvbuf говорит:

Обычно все файлы буферизуются блоками. Если поток ссылается на терминал (как это обычно делает стандартный вывод), он буферизуется строкой.

Поскольку puts() использует stdout, мы должны ожидать сброса (из-за \n).

Однако, поскольку в вашем примере stdout не использовалось до перенаправления, я предполагаю, что поведение буферизации еще не выбрано. При первой попытке записи в stdout базовый файловый дескриптор больше не является терминалом, а является обычным файлом. Я полагаю, что буферизация выбрана в этот момент.

Если вы добавите еще один вызов puts() в своем примере перед перенаправлением, то поведение буферизации будет выбрано для терминала и затем не изменится после выполнения перенаправления. В этом случае ваш пример работает так, как вы ожидаете (без явного fflush()).


изменить

Еще в man 3 setvbuf:

Когда над файлом происходит первая операция ввода-вывода, вызывается malloc(3) и создается буфер.

и далее:

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

В Linux это согласуется с вашим примером.

На странице MSDN для setvbuf:

поток должен ссылаться на открытый файл, который не подвергался операции ввода-вывода с момента его открытия.

person prog-fh    schedule 04.09.2020
comment
Определенно, это работает! Но я борюсь, почему поведение буфера еще не выбрано. - person Harrison Lee; 05.09.2020
comment
@HarrisonLee Как показывает ваш пример, я думаю, что то, выбирает ли FILE автоматически свое поведение буферизации при открытии или при первой попытке записи, является деталью реализации, на которую мы не должны полагаться. Неявное различие буфера блока/строки для неконечных потоков (как указано в man 3 setvbuf) должно быть действительным только в том случае, если поток сохраняет свои исходные свойства; здесь мы заменяем терминальный файловый дескриптор на обычный файловый дескриптор, поэтому поведение буферизации зависит только от того, когда он выбран (насколько я знаю, это не определено). - person prog-fh; 05.09.2020