Почему printf не сбрасывается после вызова, если в строке формата нет новой строки?

539

Почему printfпосле вызова не происходит сброс, если в строке формата нет новой строки? Это поведение POSIX? Как я мог printfсразу же промывать каждый раз?

Сумасшедший Ченц
источник
2
Вы исследовали, происходит ли это с любым файлом или только с терминалами? это звучит как умная функция терминала, которая не выводит незавершенную строку из фоновой программы, хотя я ожидаю, что она не будет применяться к программе переднего плана.
PypeBros
7
В Cygwin bash я вижу такое же неправильное поведение, даже если в строке формата находится символ новой строки. Эта проблема является новой для Windows 7; тот же исходный код работал нормально на Windows XP. MS cmd.exe мигает, как и ожидалось. Исправление setvbuf(stdout, (char*)NULL, _IONBF, 0)работает вокруг проблемы, но, конечно, не должно быть необходимо. Я использую MSVC ++ 2008 Express. ~~~
Стив Питчерс
9
Чтобы уточнить заголовок вопроса: сам по себе printf(..) сброс не выполняется , его буферизация stdoutможет сбрасываться при просмотре новой строки (если она буферизована строкой). Он будет реагировать так же putchar('\n');, поэтому printf(..)не является особенным в этом отношении. Это противоречит тому cout << endl;, что в документации явно упоминается промывка. В документации printf вообще не упоминается промывка.
Евгений Сергеев
1
запись (/ сброс) - потенциально дорогостоящая операция, которая, вероятно, буферизована по соображениям производительности.
hanshenrik
@EvgeniSergeev: Есть ли согласие, что вопрос неправильно диагностировал проблему, и что сброс происходит, когда в выходных данных есть новая строка ? (Поместить единицу в строку формата - это один, но не единственный способ получить единицу в выводе).
Бен Фойг

Ответы:

703

stdoutПоток линии буферном по умолчанию, так что будет отображать только то , что в буфере после того, как он достигнет новой строки (или , когда это сказано). У вас есть несколько вариантов печати сразу:

Печать на stderrвместо использования fprintf( stderrэто небуферизовано по умолчанию ):

fprintf(stderr, "I will be printed immediately");

Сбрасывайте стандартный вывод всякий раз, когда вам это нужно, используя fflush:

printf("Buffered, will be flushed");
fflush(stdout); // Will now print everything in the stdout buffer

Изменить : Из комментария Энди Росса ниже, вы также можете отключить буферизацию на стандартный вывод, используя setbuf:

setbuf(stdout, NULL);

или его безопасная версия, setvbufкак описано здесь

setvbuf(stdout, NULL, _IONBF, 0); 
Радд Зволински
источник
266
Или полностью отключить буферизацию:setbuf(stdout, NULL);
Энди Росс
80
Кроме того, я просто хотел упомянуть, что, очевидно, в UNIX символ новой строки обычно очищает буфер, только если stdout является терминалом. Если вывод перенаправляется в файл, символ новой строки не сбрасывается.
Ор
5
Я чувствую, что должен добавить: я только что проверил эту теорию, и я обнаружил, что использование setlinebuf()потока, который не направлен на терминал , сбрасывается в конце каждой строки.
Додди
8
«При первоначальном открытии стандартный поток ошибок не полностью буферизован; стандартные входные и стандартные выходные потоки полностью буферизуются в том и только в том случае, если можно определить, что поток не ссылается на интерактивное устройство» - см. Этот вопрос: stackoverflow.com / questions / 5229096 /…
Сеппо Энарви
3
@RuddZwolinski Если это будет хороший канонический ответ на вопрос «почему не печатается», важно упомянуть различие между терминалом и файлом согласно «Всегда ли printf очищает буфер при встрече с новой строкой?» прямо в этом высоко голосуемом ответе, против людей, которым нужно читать комментарии ...
HostileFork говорит, что не доверяйте SE
128

Нет, это не поведение POSIX, это поведение ISO (ну, это поведение POSIX , но лишь постольку , поскольку они соответствуют ISO).

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

myprog >myfile.txt

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

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

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

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

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

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

Раздел ISO C99 7.19.3/3является соответствующим битом:

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

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

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

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

Поддержка этих характеристик зависит от реализации, и может быть затронута через setbufи setvbufфункции.

paxdiablo
источник
8
Я только что натолкнулся на сценарий, в котором даже есть '\ n', printf () не сбрасывается. Это было преодолено добавлением fflush (stdout), как вы упомянули здесь. Но мне интересно, почему '\ n' не удалось очистить буфер в printf ().
Цян Сюй
11
@QiangXu, стандартный вывод - это линейная буферизация только в том случае, если можно окончательно определить ссылку на интерактивное устройство. Так, например, если вы перенаправляете вывод с помощью myprog >/tmp/tmpfile, это полностью буферизуется, а не буферизируется строкой. По памяти определение того, является ли ваш стандартный вывод интерактивным, оставлено на усмотрение реализации.
paxdiablo
3
более того, в Windows вызов setvbuf (...., _IOLBF) не будет работать, поскольку _IOLBF там же, что и _IOFBF: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Петр Лопусевич
28

Вероятно, так из-за эффективности и потому, что если у вас есть несколько программ, пишущих в один TTY, вы не получите чередование символов в строке. Так что, если программы A и B выводят, вы обычно получите:

program A output
program B output
program B output
program A output
program B output

Это воняет, но это лучше, чем

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

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

Южное Гостеприимство
источник
26

Чтобы немедленно сбросить вызов fflush(stdout)или fflush(NULL)( NULLозначает сбросить все).

Аарон
источник
31
Имейте в виду, fflush(NULL);как правило, очень плохая идея. Это снизит производительность, если у вас будет открыто много файлов, особенно в многопоточной среде, где вы будете бороться со всеми за блокировки.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
14

Примечание: библиотеки времени выполнения Microsoft не поддерживают буферизацию строки, поэтому printf("will print immediately to terminal"):

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf

Ренато
источник
3
Хуже , чем printfидти сразу к терминалу в «нормальном» случае является тем фактом , что printfи fprintfполучить более крупно буферные даже в тех случаях , когда их выход для непосредственного применения. Если MS не исправит ошибки, одна программа не сможет захватить stderr и stdout из другой и определить, в какой последовательности они были отправлены.
суперкат
нет, он не распечатывает это немедленно на терминал, если не была установлена ​​буферизация. По умолчанию используется полная буферизация
phuclv
12

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

Чтобы получить немедленный вывод, либо:

  1. Распечатать в stderr.
  2. Сделать стандартный вывод небуферизованным.
Дуглас Лидер
источник
10
Или fflush(stdout).
RastaJedi
2
msgstr "выводится только после печати новой строки." Не только это, но как минимум 4 других случая. буфер полный, пишите stderr(этот ответ упоминает позже), fflush(stdout), fflush(NULL).
chux - Восстановить Монику
11

по умолчанию stdout буферизуется строкой, stderr не буферизируется, а файл полностью буферизуется.

woso
источник
10

Вместо этого вы можете выполнить fprintf to stderr, который не имеет буфера. Или вы можете сбросить стандартный вывод, когда захотите. Или вы можете установить стандартный вывод для небуферизованного.

Расмус Кай
источник
10

Используйте setbuf(stdout, NULL);для отключения буферизации.

dnahc araknayirp
источник
2

Обычно существует 2 уровня

1. Кеш ядра буфера (ускоряет чтение / запись)

2. Буферизация в библиотеке ввода / вывода (уменьшает количество системных вызовов)

Давайте возьмем пример fprintf and write().

Когда вы звоните fprintf(), он не записывается напрямую в файл. Сначала он идет в буфер stdio в памяти программы. Оттуда это записывается в буферный кеш ядра с помощью системного вызова write. Таким образом, один из способов пропустить буфер ввода-вывода - напрямую использовать write (). Другие способы использования setbuff(stream,NULL). Это устанавливает режим буферизации на отсутствие буферизации, и данные напрямую записываются в буфер ядра. Чтобы принудительно переместить данные в буфер ядра, мы можем использовать «\ n», который в случае режима буферизации по умолчанию «линейной буферизации» очистит буфер ввода-вывода. Или мы можем использовать fflush(FILE *stream).

Теперь мы находимся в буфере ядра. Ядро (/ OS) хочет минимизировать время доступа к диску и, следовательно, читает / записывает только блоки диска. Поэтому, когда read()выдается a , который является системным вызовом и может быть вызван напрямую или через него fscanf(), ядро ​​считывает блок диска с диска и сохраняет его в буфере. После этого данные копируются отсюда в пространство пользователя.

Точно так же fprintf()данные, полученные из буфера ввода / вывода, записываются на диск ядром. Это делает read () write () быстрее.

Теперь, чтобы заставить ядро ​​инициировать a write(), после чего передача данных контролируется аппаратными контроллерами, есть также несколько способов. Мы можем использовать O_SYNCили похожие флаги во время записи звонков. Или мы могли бы использовать другие функции, например, fsync(),fdatasync(),sync()чтобы ядро ​​инициировало запись, как только данные будут доступны в буфере ядра.

о_О
источник