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?
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:
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.
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.
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.
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"):
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:
Drukuj do stderr.
Spraw, aby standardowe wyjście nie było buforowane.
„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.
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.
setvbuf(stdout, (char*)NULL, _IONBF, 0)
problem, ale z pewnością nie powinna być konieczna. Używam MSVC ++ 2008 Express. ~~~printf(..)
nie wykonuje żadnego spłukiwania , jest to buforowanie,stdout
które może się opróżnić, gdy zobaczysz nowy wiersz (jeśli jest buforowany w linii). Reagowałby w ten sam sposóbputchar('\n');
, więcprintf(..)
nie jest pod tym względem wyjątkowy. Jest to w przeciwieństwie do tegocout << endl;
, którego dokumentacja wyraźnie wspomina spłukiwanie. Dokumentacja printf nie wspomina w ogóle spłukiwania.Odpowiedzi:
stdout
Strumień 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:stderr
Zamiast tego drukuj za pomocąfprintf
( domyślnie niestderr
jest buforowana ):Spłucz stdout za każdym razem, gdy jest to potrzebne, aby użyć
fflush
:Edycja : Z poniższego komentarza Andy'ego Rossa możesz również wyłączyć buforowanie na standardowym wyjściu, używając
setbuf
:źródło
setbuf(stdout, NULL);
setlinebuf()
strumienia, który nie jest skierowany do terminala jest zaczerwienienie na końcu każdej linii.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
printf
się nie spłukuje, nawet jeśli zostanie wysłany nowy wiersz, na przykład: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ć
setvbuf
przed uruchomieniemstdout
, aby ustawić go na niebuforowany i nie będziesz musiał się martwić o dodanie wszystkich tychfflush
wierszy do swojego kodu: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/3
jest odpowiednim bitem:źródło
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.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:
To śmierdzi, ale jest lepsze niż
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.
źródło
Aby natychmiast opróżnić połączenie
fflush(stdout)
lubfflush(NULL)
(NULL
oznacza opróżnić wszystko).źródło
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.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
źródło
printf
natychmiastowe przejście do terminala w „normalnym” przypadku jest fakt, żeprintf
ifprintf
dostaje 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.standardowe wyjście jest buforowane, więc zostanie wydrukowane dopiero po wydrukowaniu nowego wiersza.
Aby uzyskać natychmiastowe wyjście:
źródło
fflush(stdout)
.stderr
(ta odpowiedź wspomina później)fflush(stdout)
,fflush(NULL)
.domyślnie stdout jest buforowany w linii, stderr nie jest buforowany, a plik jest całkowicie buforowany.
źródło
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.
źródło
Użyj,
setbuf(stdout, NULL);
aby wyłączyć buforowanie.źródło
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żywaniesetbuff(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 poprzezfscanf()
, 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_SYNC
lub podobnych flag podczas pisania połączeń. Lub możemy użyć innych funkcji, takich jakfsync(),fdatasync(),sync()
inicjowanie zapisu przez jądro, gdy tylko dane będą dostępne w buforze jądra.źródło