Автоматическая очистка потока при чтении ввода

Вопрос или проблема

Я прочитал в книге “Расширенное программирование в Unix-среде” следующее:

Буферизация по строкам имеет два предупреждения. Во-первых, размер буфера, который стандартная библиотека ввода-вывода использует для накопления каждой строки, фиксирован, поэтому ввод-вывод может произойти, если мы заполним этот буфер перед записью символа новой строки. Во-вторых, каждый раз, когда ввод запрашивается через стандартную библиотеку ввода-вывода из (а) небужеризованного потока или (б) потока с буферизацией по строкам (который требует запроса данных из ядра), все выходные потоки с буферизацией по строкам сбрасываются. Причина для квалификатора в (б) заключается в том, что запрашиваемые данные могут уже находиться в буфере, что не требует чтения данных из ядра. Очевидно, что любой ввод из небужеризованного потока, пункт (а), требует получения данных из ядра.

https://www.gnu.org/software/libc/manual/html_node/Flushing-Buffers.html утверждает:

Сброс выходных данных из буферизованного потока означает передачу всех накопленных символов в файл. Существует множество случаев, когда буферизованный вывод на потоке автоматически сбрасывается:

Когда вы пытаетесь сделать вывод, и выходной буфер заполнен.

Когда поток закрывается. См. Закрытие потоков.

Когда программа завершается вызовом exit. См. Нормальное завершение.

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

Когда любая операция ввода на любом потоке фактически считывает данные из его файла.

Итак, я написал программу, чтобы протестировать это:

#include <stdio.h>
#include <unistd.h>

int
main()
{
    printf("Привет");
    FILE *fp = fopen("hugefile", "r");
    if (fp == NULL) {
        fprintf(stderr, "Не удается открыть файл\n");
        return -1;
    }
    setvbuf(fp, NULL, _IONBF, 0);
    sleep(2);
    char buf[10];

    while (fread(buf, sizeof(char), 10, fp) == 10) {
    }

    if (ferror(fp)) {
        fprintf(stderr, "Ошибка чтения\n");
        return -1;
    }; // Я ожидаю, что "Привет" появится на экране, но этого не происходит
    fclose(fp);
    sleep(50);
}

Но fread() не вызывает сброса вывода. Я что-то упускаю?

Я не знаю, что может иметь значение здесь, но я использую

gcc версия 14.2.1 20240910

glibc 2.40+r16+gaa533d58ff-2

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

Сброс стандартного вывода при чтении из стандартного ввода:

Вы можете добиться такого поведения, как в следующем buf.c:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
  printf("Привет");
  sleep(2);
  fprintf(stderr, "Нет вывода из stdout через 2 секунды\n");

  char buf[10];
  while (fread(buf, sizeof(char), 10, stdin) == 10) {
  }

  sleep(5);
}

Запустите это:

$ ./buf
Нет вывода из stdout через 2 секунды
Привет

Строка “Привет” распечатана в stdout и не заканчивается символом новой строки
(\n), поэтому она не сбрасывается через 2 секунды, но сбрасывается, когда
fread() выполнен. Если бы она была удалена, она была бы сброшена только после
последнего сна.

Когда любая операция ввода на любом потоке фактически считывает данные из его
файла.

Сброс в файл при чтении из файла:

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

#include <stdio.h>
#include <unistd.h>

int main(void)
{
  FILE *fp = fopen("FILE", "a");
  fprintf(fp, "добавить больше символов в FILE\n");
  fprintf(stderr, "записано больше символов в FILE\n");
  sleep(5);

  fprintf(stderr, "чтение теперь начнется\n");
  char buf[10];
  while (fread(buf, sizeof(char), 10, fp) == 10) {

  }

  fprintf(stderr, "Сон на 5 секунд перед sleep()\n");
  sleep(5);
  fprintf(stderr, "Выход\n");
}

Запустите это так, я создам небольшой файл FILE, который будет содержать только текущую дату
и запущу бинарник в фоновом режиме, чтобы я мог видеть
все сообщения, которые он печатает, и одновременно смогу изучить содержимое файла FILE:

$ date > FILE
$ ./buf &
[1] 14522
записано больше символов в FILE
$ cat FILE
Сб Nov  9 13:02:28 CET 2024
$ чтение теперь начнется
Сон на 5 секунд перед sleep()

$ cat FILE
Сб Nov  9 13:02:28 CET 2024
добавить больше символов в FILE
Выход

Как вы видите, строка добавить больше символов в FILE была добавлена в файл
только после того, как была выполнена fread().

Ответ или решение

Автоматическое сброс буфера при чтении ввода в C

В операционных системах Unix основным механизмом для работы с выводом является система буферизации ввода-вывода (I/O buffering). В данном случае нас интересует автоматическое сброс буфера при чтении данных из потока. Рассмотрим, как это работает, и почему ваше ожидание не подтвердилось в приведенном примере кода.

Основы работы с буферизацией

Когда программа выполняет операции с потоками, данные могут храниться в память (буфере) до тех пор, пока не произойдут определённые события, такие как достижение конца строки или заполнение буфера. В вашем случае, с помощью printf("Hello"); вы выводите текст в стандартный поток stdout, который обычно является строковым, или по умолчанию полностью буферизованным потоком, если происходит вывод не через stderr.

Согласно стандартной библиотеке C, вывод осуществляется при следующих условиях:

  1. При заполнении буфера;
  2. При закрытии потока;
  3. При завершении программы с помощью exit;
  4. При записи символа новой строки (\n) в поток, который использует построчную буферизацию;
  5. При операции чтения из любого потока, которая фактически требует считывания данных из файла (или устройства).

Анализ вашего кода

Вы написали следующую часть кода:

printf("Hello");
FILE *fp = fopen("hugefile", "r");
// ...
fread(buf, sizeof(char), 10, fp);

В данном коде строка "Hello" не будет отображаться на экране немедленно из-за того, что она не завершается символом новой строки (\n). Следовательно, stdout не будет сброшен в момент вызова printf, а будет ждать, когда буфер будет заполнен или когда произойдет другое условие для сброса, например, чтение из другого потока.

Ваша установка буфера как unbuffered с помощью setvbuf(fp, NULL, _IONBF, 0); применяется к файлу fp, а не к стандартному выводу. Следовательно, этот вызов не повлияет на поведение stdout.

Пример автоматического сброса

Попробуйте изменить ваш код, чтобы включить вызов fflush(stdout); после printf, чтобы явно сбросить буфер:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Hello");
    fflush(stdout); // Явный сброс буфера stdout
    FILE *fp = fopen("hugefile", "r");
    if (fp == NULL) {
        fprintf(stderr, "Cannot open file\n");
        return -1;
    }

    setvbuf(fp, NULL, _IONBF, 0);
    sleep(2);
    char buf[10];

    while (fread(buf, sizeof(char), 10, fp) == 10) {
    }

    if (ferror(fp)) {
        fprintf(stderr, "Read error\n");
        return -1;
    };

    fclose(fp);
    sleep(50);
}

Как результат, строка "Hello" отобразится на экране до того, как вы начнете читать из файла hugefile.

Заключение

Важно помнить, что поведение буферизации может варьироваться в зависимости от среды выполнения и настроек потоков. Автоматическое сбрасывание происходит в ответ на определенные действия (например, ввод), и если вы хотите контролировать это поведение, стоит использовать явные вызовы fflush(stdout); в нужных местах вашего кода.

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

Оцените материал
Добавить комментарий

Капча загружается...