Буферизация данных в С

Всем привет. Интересует следующий вопрос. Ввод/вывод на стандартные потоки в С (stdin/stdout) буферизированный, это означает, что вся информация которая поступает на эти потоки накапливается в буфере и потом записывается в файл (консоль). Прочитал, что запись происходит, когда буфер заполняется полностью. Какой размер буфера? Думаю, размер колеблется в районе пару килобайт, но тогда почему если, например, идет функция printf() и за ней scanf(), то на экран выводится то, что передается в printf(), вывод же буферизированный и буфер не заполнился полностью или есть еще моменты когда содержимое из буфера записывается в файл? К этому так же не могу понять зачем нужен fflash(). Объясните, пожалуйста, простым языком. Спасибо


Ответы (2 шт):

Автор решения: AlexGlebe

Буфер входа и выхода синхронизирован по-умолчанию. Это значит, что любая последовательность с разных потоков должна быть с сохранённой последовательностью. Например если вы кидаете в буфер "введите x:" и нажали 10 , то вывод должен быть точно "введите x:10", а не "10 введите x:".

Если scanf запрашивает данные, то он по-умолчанию выводит нажатые буквы и буфер вывода синхронизируется с помощью fflush и выводится введите x:. Можно отключить вывод на экран вводимые символы, тогда чтобы пользователь понял, что от него что-то хотят, то нужно очистить буфер иначе на экране не будет запроса введите x:. Так происходит при запросе пароля например.

Если вывод происходит в файл, то данные записываются в буфер Си, но не записываются в буфер операционной системы. При синхронизации буфер Си очищается и записывается в буфер ОС. Так больше шансов, что данные в файл запишутся при крититической ситуации. (электричество/память/процессор)

→ Ссылка
Автор решения: Fat-Zer

Стандарт Си определяет три вида буферизации потоков¹:

  • Небуферизированные потоки (unbuffered) — буферизации на стороне программы/libc как таковой нет: все данные по возможности сразу передаются ОС/устройству.
  • Полностью буферизированные (fully buffered) — данные записываются в буфер и будут фактически выведены только при его заполнении или при его принудительном сбросе.
  • Линейно-буферизированные (line buffered) — данные буферизируются, но выводятся, если в потоке встретится символ конца строки (\n), если буфер будет полностью заполнен или принудительно сброшен. Также все линейно-буферизированные потоки вывода (в частности stdout) сбрасываются, когда какой-либо (обычно другой, например, stdin) линейно-буферизированный поток пытается что-либо прочитать, а его буфер ввода пуст.

Для стандартных потоков также гарантируется:

  • stderr не является полностью буферизированным. На практике почти во всех реализациях он будет небуферезированным.

  • stdin и stdout могут быть полностью буферизированными только если они не указывают на интерактивное устройство (т.е. ввод/вывод перенаправлен в файл, а не ведётся с консоли/терминала). Почти на всех POSIX-системах они линейно-буферизированyые. Но реализация стандартной библиотеки в msvc не поддерживает линейно-буферизированные потоки, так что там они небуферизированные.

Поменять тип буферизации и задать свой буфер можно с помощью setvbuf().


Какой размер буфера?

Зависит от реализации, он доступен в макросе BUFSIZ. Размер гарантированно больше 256, типовые значения: 512—8k. Но внутренняя структура и алгоритм работы этого буфера не специализированы, так что это не значит, что в/из файла будут читаться/писаться именно столько байтов.

тогда почему если, например, идет функция printf() и за ней scanf(), то на экран выводится то, что передается в printf(), вывод же буферизированный и буфер не заполнился полностью

Как написано выше, по умолчанию потоки связанные с терминалом или линейно-буферизированные или небуферизированные. В первом случае вывод сразу во время printf() попадает на экран, а во втором — или сразу, если printf() заканчивается новой строкой, или при выполнении scanf(). Разницу в поведении можно увидеть на таком примере:

printf("Hello ");
sleep(10);
printf("world!\n");

На win32 выведется Hello и через 10 секунд world!, а на *nix — всё сразу через 10 секунд.

или есть еще моменты когда содержимое из буфера записывается в файл?

Файлы в отличие от терминала обычно полностью буферизированны, например в таком варианте:

FILE *f = fopen("foo", "w")
fprintf(f, "Hello world!\n");
getc(stdin);
fclose(f);

Во время задержки на getc() файл будет пустой.

Кроме случая полного заполнения буфер сбрасывается принудительно при закрытии файла (fclose()), корректном завершении программы (exit() или return из main(), но не обязательно при abort()). Также некоторые другие стандартные функции могут вызывать fflush(), например fseek() или функции чтения из файла, открытого для чтения/записи, но это уже зависит от реализации.

К этому так же не могу понять зачем нужен fflash().

fflush() собственно нужен, чтобы принудительно записать содержимое буфера в файл. В примере выше, если добавить fflush() перед getc(), то "Hello world!" будет гарантированно записано в файл. Если немного больше придираться к терминам, то по fflush() содержимое буфера «передаётся среде исполнения», на практике это не означает, что оно гарантированно окажется сохранено на диске т.к. у ОС есть свои средства буфферизации.

Также стоит запомнить, что fflush() влияет только на буфер вывода он не оказывает ни какого эффекта на потоки ввода..


¹ Вообще говоря всё это определено и для потоков ввода, и для потоков вывода, но на практике на поведение потоков ввода это практически ни как не отражается.

→ Ссылка