Чому 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: Чи існує думка про те, що питання неправильно діагностувало проблему, і що промивання відбувається, коли у виході є новий рядок ? (розміщення одного у рядку формату - це один із способів, але не єдиний спосіб отримання одного у висновку).
Бен Войгт

Відповіді:


702

stdoutПотік лінії буферном за замовчуванням, так що буде відображати тільки те , що в буфері після того, як він досягне нового рядка (або , коли це сказано). У вас є кілька варіантів для друку негайно:

Роздрукувати stderrзамість цього з використанням fprintf( stderrне буферизовано за замовчуванням ):

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

Промивайте stdout кожного разу, коли вам потрібно fflush:

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

Редагувати : З коментаря Енді Росса нижче, ви також можете відключити буферизацію в stdout, використовуючи setbuf:

setbuf(stdout, NULL);

266
Або повністю відключити буферизацію:setbuf(stdout, NULL);
Енді Росс

80
Крім того, я просто хотів зазначити, що, мабуть, в UNIX новий рядок, як правило, промиває буфер лише, якщо stdout є терміналом. Якщо вихідний файл переспрямовується на файл, новий рядок не буде спалахувати.
hora

5
Я відчуваю, що я повинен додати: я щойно тестував цю теорію, і я виявляв, що використання setlinebuf()в потоці, який не спрямований до терміналу , проливається в кінці кожного рядка.
Додді

8
"Спочатку відкрився, стандартний потік помилок не буферизований повністю; стандартний потік вводу та стандартний вихідний потік буферизуються повністю лише тоді, і лише тоді, коли потік можна визначити не відноситься до інтерактивного пристрою" - див. Це питання: stackoverflow.com / питання / 5229096 /…
Seppo Enarvi

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.


8
Щойно я натрапив на сценарій, коли навіть є "\ n", printf () не змикається. Це було подолано додаванням флюша (stdout), про який ви згадали тут. Але мене цікавить причина, чому "\ n" не вдалося промити буфер у printf ().
Цянь Сю

11
@QiangXu, стандартний вихід буферизований у рядку лише у тому випадку, коли його можна остаточно визначити для посилання на інтерактивний пристрій. Так, наприклад, якщо ви переспрямовуєте висновок з myprog >/tmp/tmpfile, це повністю буферний, а не буферний рядок. З пам’яті визначення реалізації того, чи є ваш стандартний вихід інтерактивним, залишається на реалізації.
paxdiablo

3
крім того, у Windows виклик setvbuf (...., _IOLBF) не працюватиме, оскільки _IOLBF такий же, як _IOFBF там: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

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 з іншої та визначати, у якій послідовності речі надсилалися кожному.
supercat

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

12

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

Щоб отримати негайний вихід, будь-ласка:

  1. Роздрукувати в stderr.
  2. Зробіть stdout нерозподіленим.

10
Або fflush(stdout).
RastaJedi

2
"так виводиться лише після друку нового рядка." Не тільки це, але щонайменше 4 інші випадки. буфер повний, пишіть stderr(ця відповідь згадує пізніше), fflush(stdout), fflush(NULL).
chux

11

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


10

Ви можете замість цього зробити fprintf до stderr, який розблоковується. Або ви можете промити stdout, коли захочете. Або ви можете встановити stdout на небуферовані.



2

Зазвичай існує 2 рівня буферних-

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

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

Візьмемо для прикладу fprintf and write().

Коли ви телефонуєте fprintf(), він не передається безпосередньо до файлу. Він спочатку переходить до буфера stdio в пам'яті програми. Звідси записується в кеш-пам'ять ядра за допомогою системного виклику write. Тож один із способів пропустити буфер вводу / виводу - це безпосередньо за допомогою write (). Інші способи - за допомогою setbuff(stream,NULL). Це встановлює режим буферизації без буферизації, а дані записуються безпосередньо в буфер ядра. Щоб змусити перенести дані в буфер ядра, ми можемо використовувати "\ n", який у випадку буферизації за замовчуванням режиму 'буферування рядків' буде змивати буфер вводу / виводу. Або ми можемо використовувати fflush(FILE *stream).

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

Аналогічно, що fprintf()дані , отримані з буфера вводу / виводу, записуються на диск ядром. Це змушує читати () писати () швидше.

Тепер, щоб змусити ядро ​​ініціювати write(), після чого передачу даних контролюють апаратні контролери, є також деякі способи. Ми можемо використовувати O_SYNCабо подібні прапори під час запиту дзвінків. Або ми могли б використовувати інші функції, як, fsync(),fdatasync(),sync()щоб змусити ядро ​​ініціювати запис, як тільки дані з’являться в буфері ядра.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.