Dlaczego printf nie opróżnia się po wywołaniu, chyba że nowa linia znajduje się w ciągu formatu?


539

Dlaczego printfnie opróżnia się po wywołaniu, chyba że nowa linia znajduje się w ciągu formatu? Czy to zachowanie POSIX? Jak mogę printfnatychmiast spłukiwać za każdym razem?


2
sprawdziłeś, czy dzieje się tak z jakimkolwiek plikiem, czy tylko z terminalami? że brzmiałoby być sprytna funkcja terminal nie do wyjściowego niezakończonej linii od programu w tle, choć można oczekiwać, że nie będzie ubiegać się o programie pierwszym planie.
PypeBros,

7
Pod Bash Cygwin widzę to samo zachowanie, nawet jeśli nowa linia jest w ciągu formatu. Ten problem jest nowy w systemie Windows 7; ten sam kod źródłowy działał poprawnie w systemie Windows XP. MS cmd.exe opróżnia się zgodnie z oczekiwaniami. Poprawka rozwiązuje setvbuf(stdout, (char*)NULL, _IONBF, 0)problem, ale z pewnością nie powinna być konieczna. Używam MSVC ++ 2008 Express. ~~~
Steve Pitchers

9
Aby wyjaśnić tytuł pytania: printf(..) nie wykonuje żadnego spłukiwania , jest to buforowanie, stdoutktóre może się opróżnić, gdy zobaczysz nowy wiersz (jeśli jest buforowany w linii). Reagowałby w ten sam sposób putchar('\n');, więc printf(..)nie jest pod tym względem wyjątkowy. Jest to w przeciwieństwie do tego cout << endl;, którego dokumentacja wyraźnie wspomina spłukiwanie. Dokumentacja printf nie wspomina w ogóle spłukiwania.
Evgeni Sergeev

1
pisanie (/ flushing) jest potencjalnie kosztowną operacją, prawdopodobnie buforowaną ze względu na wydajność.
hanshenrik

@EvgeniSergeev: Czy istnieje zgoda co do tego, że pytanie nieprawidłowo zdiagnozowało problem i że zaczerwienienie występuje, gdy na wyjściu jest nowa linia ? (umieszczenie jednego w ciągu formatu jest jednym ze sposobów, ale nie jedynym, aby uzyskać jeden w wyniku).
Ben Voigt

Odpowiedzi:


702

stdoutStrumień jest linia buforowana domyślnie, więc będą wyświetlane tylko co jest w buforze po osiągnięciu nowego wiersza (lub gdy jest to powiedziano). Masz kilka opcji do natychmiastowego wydrukowania:

stderrZamiast tego drukuj za pomocą fprintf( domyślnie niestderr jest buforowana ):

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

Spłucz stdout za każdym razem, gdy jest to potrzebne, aby użyć fflush:

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

Edycja : Z poniższego komentarza Andy'ego Rossa możesz również wyłączyć buforowanie na standardowym wyjściu, używając setbuf:

setbuf(stdout, NULL);

266
Lub, aby całkowicie wyłączyć buforowanie:setbuf(stdout, NULL);
Andy Ross,

80
Chciałem też wspomnieć, że najwyraźniej w systemie UNIX nowa linia zwykle opróżnia bufor tylko wtedy, gdy stdout jest terminalem. Jeśli dane wyjściowe są przekierowywane do pliku, nowa linia nie zostanie opróżniona.
hora

5
Czuję, że powinienem dodać: Właśnie testuje tę teorię, a ja stwierdzając, że za pomocą setlinebuf()strumienia, który nie jest skierowany do terminala jest zaczerwienienie na końcu każdej linii.
Doddy

8
„Po początkowym otwarciu standardowy strumień błędów nie jest w pełni buforowany; standardowe strumienie wejściowe i standardowe strumienie wyjściowe są w pełni buforowane tylko wtedy, gdy można ustalić, że strumień nie odnosi się do urządzenia interaktywnego” - patrz pytanie: stackoverflow.com / pytania / 5229096 /…
Seppo Enarvi

3
@ RuddZwolinski Jeśli to będzie dobra odpowiedź kanoniczna na pytanie „dlaczego nie jest drukowane”, wydaje się ważne, aby wspomnieć o rozróżnieniu terminala / pliku zgodnie z „Czy printf zawsze opróżnia bufor po napotkaniu nowej linii?” bezpośrednio w tej wysoce uprzywilejowanej odpowiedzi, a ludzie potrzebujący przeczytać komentarze ...
HostileFork mówi: nie ufaj SE

128

Nie, to nie jest zachowanie POSIX, to zachowanie ISO (cóż, jest zachowanie POSIX ale tylko o ile są one zgodne z ISO).

Standardowe wyjście jest buforowane liniowo, jeśli można je wykryć w odniesieniu do urządzenia interaktywnego, w przeciwnym razie jest ono w pełni buforowane. Są więc sytuacje, w których printfsię nie spłukuje, nawet jeśli zostanie wysłany nowy wiersz, na przykład:

myprog >myfile.txt

Ma to sens ze względu na wydajność, ponieważ jeśli wchodzisz w interakcję z użytkownikiem, prawdopodobnie chce zobaczyć każdą linię. Jeśli wysyłasz dane wyjściowe do pliku, najprawdopodobniej na drugim końcu nie ma użytkownika (choć nie jest to niemożliwe, może on modyfikować plik). Teraz już mógł twierdzić, że użytkownik chce zobaczyć każdy znak, ale istnieją dwa problemy z tym.

Po pierwsze, nie jest bardzo wydajny. Po drugie, pierwotny mandat ANSI C miał przede wszystkim kodyfikować istniejące zachowanie, a nie wynajdować nowe , a decyzje projektowe zostały podjęte na długo przed rozpoczęciem procesu przez ANSI. Nawet ISO postępuje obecnie bardzo ostrożnie, zmieniając istniejące reguły w normach.

Co do tego, jak sobie z tym poradzić, jeśli fflush (stdout)po każdym wywołaniu wyjściowym, które chcesz natychmiast zobaczyć, rozwiąże to problem.

Alternatywnie możesz użyć setvbufprzed uruchomieniem stdout, aby ustawić go na niebuforowany i nie będziesz musiał się martwić o dodanie wszystkich tych fflushwierszy do swojego kodu:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Pamiętaj, że jeśli tak, to może to wpłynąć na wydajność wysyłania danych do pliku. Należy również pamiętać, że wsparcie dla tego jest zdefiniowane w implementacji, nie jest gwarantowane przez standard.

Sekcja ISO C99 7.19.3/3jest odpowiednim bitem:

Gdy strumień nie jest buforowany , postacie powinny pojawić się jak najszybciej ze źródła lub w miejscu docelowym. W przeciwnym razie znaki mogą być gromadzone i przesyłane do lub ze środowiska hosta jako blok.

Gdy strumień jest w pełni buforowany , znaki mają być przesyłane do lub ze środowiska hosta jako blok, gdy bufor jest zapełniony.

Gdy strumień jest buforowany w linii , znaki mają być przesyłane do lub ze środowiska hosta jako blok, gdy napotkany zostanie znak nowej linii.

Ponadto znaki mają być przesyłane jako blok do środowiska hosta, gdy bufor jest zapełniony, gdy żądanie jest przesyłane w niebuforowanym strumieniu lub gdy żądanie jest wprowadzane w strumieniu buforowanym w linii, który wymaga transmisji znaków ze środowiska hosta .

Obsługa tych cech jest zdefiniowana w ramach implementacji i może mieć na nie wpływ funkcje setbufi setvbuf.


8
Właśnie natknąłem się na scenariusz, w którym nawet istnieje „\ n”, printf () się nie opróżnia. Zostało to rozwiązane przez dodanie flflush (stdout), jak wspomniano tutaj. Zastanawiam się jednak, dlaczego „\ n” nie opróżnił bufora w printf ().
Qiang Xu

11
@QiangXu, standardowe wyjście jest buforowane w linii tylko w przypadku, gdy można definitywnie określić, że odnosi się do urządzenia interaktywnego. Na przykład, jeśli przekierujesz dane wyjściowe za pomocą myprog >/tmp/tmpfile, będzie to w pełni buforowane, a nie buforowane liniowo. Z pamięci określenie, czy standardowe wyjście jest interaktywne, należy do implementacji.
paxdiablo

3
ponadto w systemie Windows wywołanie setvbuf (...., _IOLBF) nie będzie działać, ponieważ _IOLBF jest taki sam jak _IOFBF tam: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

28

Prawdopodobnie jest tak z powodu wydajności i ponieważ jeśli masz wiele programów zapisujących do jednego TTY, w ten sposób nie pojawi się znak w linii z przeplotem. Więc jeśli program A i B generują dane, zwykle otrzymasz:

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

To śmierdzi, ale jest lepsze niż

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Pamiętaj, że nie ma nawet gwarancji, że spłukujesz na nowej linii, więc powinieneś spłukać jawnie, jeśli spłukanie ma dla Ciebie znaczenie.


26

Aby natychmiast opróżnić połączenie fflush(stdout)lub fflush(NULL)( NULLoznacza opróżnić wszystko).


31
Pamiętaj, że fflush(NULL);jest to zwykle bardzo zły pomysł. Zabije wydajność, jeśli masz otwartych wiele plików, szczególnie w środowisku wielowątkowym, w którym będziesz walczył ze wszystkim o zamki.
R .. GitHub ZATRZYMAJ LÓD

14

Uwaga: biblioteki wykonawcze Microsoft nie obsługują buforowania linii, więc printf("will print immediately to terminal"):

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


3
Gorsze niż printfnatychmiastowe przejście do terminala w „normalnym” przypadku jest fakt, że printfi fprintfdostaje się gorzej buforowane nawet w przypadkach, gdy ich dane wyjściowe są natychmiast wykorzystywane. O ile MS nie naprawiło rzeczy, uniemożliwi to jednemu programowi przechwycenie stderr i stdout od drugiego i ustalenie, w jakiej kolejności rzeczy zostały wysłane do każdego.
supercat

nie, nie drukuje tego natychmiast na terminalu, chyba że nie ustawiono buforowania. Domyślnie używane jest pełne buforowanie
phuclv

12

standardowe wyjście jest buforowane, więc zostanie wydrukowane dopiero po wydrukowaniu nowego wiersza.

Aby uzyskać natychmiastowe wyjście:

  1. Drukuj do stderr.
  2. Spraw, aby standardowe wyjście nie było buforowane.

10
Lub fflush(stdout).
RastaJedi

2
„tak będzie wyświetlać się dopiero po wydrukowaniu nowego wiersza”. Nie tylko to, ale co najmniej 4 inne przypadki. bufor pełny, napisz do stderr(ta odpowiedź wspomina później) fflush(stdout), fflush(NULL).
chux - Przywróć Monikę

11

domyślnie stdout jest buforowany w linii, stderr nie jest buforowany, a plik jest całkowicie buforowany.


10

Możesz fprintf do stderr, który nie jest buforowany. Lub możesz spłukać standardowe wejście, kiedy chcesz. Lub możesz ustawić stdout na niebuforowany.



2

Są ogólnie 2 poziomy buforowania

1. Pamięć podręczna bufora jądra (przyspiesza odczyt / zapis)

2. Buforowanie w bibliotece I / O (zmniejsza liczbę wywołań systemowych)

Weźmy przykład fprintf and write().

Kiedy dzwonisz fprintf(), nie łączy się bezpośrednio z plikiem. Najpierw trafia do bufora stdio w pamięci programu. Stamtąd jest zapisywany w buforze bufora jądra za pomocą zapisu systemowego. Tak więc jednym ze sposobów pominięcia bufora we / wy jest użycie write (). Inne sposoby to używanie setbuff(stream,NULL). To ustawia tryb buforowania na brak buforowania, a dane są zapisywane bezpośrednio w buforze jądra. Aby wymusić przeniesienie danych do bufora jądra, możemy użyć „\ n”, który w przypadku domyślnego trybu buforowania „buforowania linii” opróżnia bufor We / Wy. Lub możemy użyć fflush(FILE *stream).

Teraz jesteśmy w buforze jądra. Jądro (/ OS) chce zminimalizować czas dostępu do dysku, a zatem czyta / zapisuje tylko bloki dysku. Kiedy więc read()zostanie wydane a, które jest wywołaniem systemowym i można je wywołać bezpośrednio lub poprzez fscanf(), jądro odczytuje blok dysku z dysku i przechowuje go w buforze. Następnie dane są kopiowane stąd do przestrzeni użytkownika.

Podobnie fprintf()dane otrzymane z bufora we / wy są zapisywane na dysku przez jądro. To sprawia, że ​​read () write () jest szybszy.

Teraz, aby zmusić jądro do zainicjowania a write(), po którym transfer danych jest kontrolowany przez kontrolery sprzętowe, są też pewne sposoby. Możemy używać O_SYNClub podobnych flag podczas pisania połączeń. Lub możemy użyć innych funkcji, takich jak fsync(),fdatasync(),sync()inicjowanie zapisu przez jądro, gdy tylko dane będą dostępne w buforze jądra.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.