Warum wird printf nach dem Aufruf nicht geleert, es sei denn, eine neue Zeile befindet sich in der Formatzeichenfolge?
539
Warum wird printfnach dem Aufruf nicht geleert, es sei denn, eine neue Zeile befindet sich in der Formatzeichenfolge? Ist das POSIX-Verhalten? Wie könnte ich printfjedes Mal sofort spülen?
Haben Sie untersucht, ob dies bei einer Datei oder nur bei Terminals der Fall ist? Das klingt nach einer cleveren Terminalfunktion, um keine unvollständige Zeile aus einem Hintergrundprogramm auszugeben, obwohl ich davon ausgehe, dass dies nicht für das Vordergrundprogramm gilt.
PypeBros
7
Unter Cygwin bash da sehe ich das gleiche Fehlverhalten , auch wenn ein Newline ist im Format - String. Dieses Problem ist neu in Windows 7. Der gleiche Quellcode funktionierte unter Windows XP einwandfrei. MS cmd.exe wird wie erwartet gespült. Das Update setvbuf(stdout, (char*)NULL, _IONBF, 0)umgeht das Problem, sollte aber sicherlich nicht notwendig gewesen sein. Ich verwende MSVC ++ 2008 Express. ~~~
Steve Pitchers
9
Um den Titel der Frage zu verdeutlichen: Führt printf(..)kein Leeren selbst durch, es ist die Pufferung stdout, die beim Anzeigen einer neuen Zeile (wenn sie zeilengepuffert ist) möglicherweise geleert wird. Es würde genauso reagieren putchar('\n');, printf(..)ist also in dieser Hinsicht nicht besonders. Dies steht im Gegensatz zu cout << endl;, in dessen Dokumentation das Spülen deutlich erwähnt wird. In der Dokumentation von printf wird das Spülen überhaupt nicht erwähnt.
Evgeni Sergeev
1
Schreiben (/ Leeren) ist möglicherweise eine teure Operation, die wahrscheinlich aus Leistungsgründen gepuffert wird.
Hanshenrik
@ EvgeniSergeev: Gibt es einen Konsens darüber, dass die Frage das Problem falsch diagnostiziert hat und dass eine Spülung auftritt, wenn die Ausgabe eine neue Zeile enthält ? (Das Einfügen eines in die Formatzeichenfolge ist eine, aber nicht die einzige Möglichkeit, eine in die Ausgabe aufzunehmen).
Ben Voigt
Antworten:
702
Der stdoutStream ist standardmäßig zeilengepuffert und zeigt daher erst an, was sich im Puffer befindet, nachdem er eine neue Zeile erreicht hat (oder wenn er dazu aufgefordert wird). Sie haben einige Optionen zum sofortigen Drucken:
Oder, um die Pufferung vollständig zu deaktivieren:setbuf(stdout, NULL);
Andy Ross
80
Ich wollte nur erwähnen, dass anscheinend unter UNIX eine neue Zeile den Puffer normalerweise nur leert, wenn stdout ein Terminal ist. Wenn die Ausgabe in eine Datei umgeleitet wird, wird eine neue Zeile nicht geleert.
Hora
5
Ich denke, ich sollte hinzufügen: Ich habe gerade diese Theorie getestet und stelle fest, dass die Verwendung setlinebuf()in einem Stream, der nicht auf ein Terminal gerichtet ist, am Ende jeder Zeile gespült wird.
Doddy
8
"Beim ersten Öffnen ist der Standardfehlerstrom nicht vollständig gepuffert. Die Standardeingabe- und Standardausgabestreams werden genau dann vollständig gepuffert, wenn festgestellt werden kann, dass der Stream nicht auf ein interaktives Gerät verweist" - siehe diese Frage: stackoverflow.com / Fragen / 5229096 /…
Seppo Enarvi
3
@RuddZwolinski Wenn dies eine gute Canon-Antwort auf "Warum wird nicht gedruckt" sein soll, scheint es wichtig zu sein, die Unterscheidung zwischen Terminal und Datei gemäß "Löscht printf immer den Puffer, wenn eine neue Zeile auftritt?" Zu erwähnen. direkt in dieser hoch bewerteten Antwort, gegen Leute, die die Kommentare lesen müssen ...
HostileFork sagt, traue SE
128
Nein, es ist kein POSIX-Verhalten, es ist ISO-Verhalten (nun, es ist POSIX-Verhalten, aber nur insoweit, als sie ISO-konform sind).
Die Standardausgabe ist zeilengepuffert, wenn erkannt werden kann, dass sie sich auf ein interaktives Gerät bezieht, andernfalls ist sie vollständig gepuffert. Es gibt also Situationen, in denen printfnicht gespült wird, selbst wenn eine neue Zeile gesendet werden muss, z.
myprog >myfile.txt
Dies ist aus Effizienzgründen sinnvoll, da bei der Interaktion mit einem Benutzer wahrscheinlich jede Zeile angezeigt werden soll. Wenn Sie die Ausgabe an eine Datei senden, befindet sich höchstwahrscheinlich kein Benutzer am anderen Ende (obwohl dies nicht unmöglich ist, könnte er die Datei verfolgen). Jetzt Sie könnten argumentieren, dass der Benutzer jeden Charakter sehen möchte, aber es gibt zwei Probleme damit.
Das erste ist, dass es nicht sehr effizient ist. Das zweite ist, dass das ursprüngliche ANSI C-Mandat darin bestand, in erster Linie vorhandenes Verhalten zu kodifizieren , anstatt neues Verhalten zu erfinden , und diese Entwurfsentscheidungen wurden lange vor Beginn des Prozesses durch ANSI getroffen. Selbst ISO geht heutzutage sehr vorsichtig vor, wenn bestehende Regeln in den Standards geändert werden.
Wie Sie damit umgehen sollen, wenn Sie fflush (stdout) nach jedem Ausgabeaufruf, den Sie sofort sehen möchten, , wird das Problem gelöst.
Alternativ können Sie verwenden , setvbufvor dem Betrieb auf stdout, um es zu ungepufferten zu setzen und Sie werden nicht über das Hinzufügen all diese Sorgen haben fflushLinien , um Ihren Code:
setvbuf (stdout, NULL, _IONBF, BUFSIZ);
Denken Sie daran , dass ziemlich viel Einfluss auf die Leistung, wenn Sie sind die Ausgabe in eine Datei zu senden. Beachten Sie auch, dass die Unterstützung dafür implementierungsdefiniert ist und nicht durch den Standard garantiert wird.
Der Abschnitt ISO C99 7.19.3/3ist das relevante Bit:
Wenn ein Stream ungepuffert ist , sollen Zeichen so schnell wie möglich von der Quelle oder vom Ziel angezeigt werden. Andernfalls können Zeichen akkumuliert und als Block zur oder von der Hostumgebung übertragen werden.
Wenn ein Stream vollständig gepuffert ist , sollen Zeichen als Block zur oder von der Host-Umgebung übertragen werden, wenn ein Puffer gefüllt ist.
Wenn ein Stream zeilengepuffert ist , sollen Zeichen als Block zur oder von der Host-Umgebung übertragen werden, wenn ein neues Zeilenzeichen auftritt.
Darüber hinaus sollen Zeichen als Block an die Hostumgebung übertragen werden, wenn ein Puffer gefüllt ist, wenn eine Eingabe in einem ungepufferten Stream angefordert wird oder wenn eine Eingabe in einem zeilengepufferten Stream angefordert wird, der die Übertragung von Zeichen aus der Hostumgebung erfordert .
Die Unterstützung für diese Merkmale ist implementierungsdefiniert und kann über die Funktionen setbufund beeinflusst setvbufwerden.
Ich bin gerade auf ein Szenario gestoßen, in dem printf () selbst bei einem '\ n' nicht geleert wird. Es wurde überwunden, indem ein Flush (stdout) hinzugefügt wurde, wie Sie hier erwähnt haben. Aber ich frage mich, warum '\ n' den Puffer in printf () nicht leeren konnte.
Qiang Xu
11
@QiangXu, Standardausgabe wird nur in dem Fall zeilengepuffert, in dem definitiv festgestellt werden kann, dass sie sich auf ein interaktives Gerät bezieht. Wenn Sie beispielsweise die Ausgabe mit umleiten myprog >/tmp/tmpfile, ist diese vollständig gepuffert und nicht zeilengepuffert. Aus dem Speicher bleibt die Bestimmung, ob Ihre Standardausgabe interaktiv ist, der Implementierung überlassen.
Das liegt wahrscheinlich an der Effizienz und daran, dass bei mehreren Programmen, die in ein einzelnes TTY schreiben, auf diese Weise keine Zeichen in einer Interlaced-Zeile angezeigt werden. Wenn also Programm A und B ausgegeben werden, erhalten Sie normalerweise:
program A output
program B output
program B output
program A output
program B output
Das stinkt, aber es ist besser als
proprogrgraam m AB ououtputputt
prproogrgram amB A ououtputtput
program B output
Beachten Sie, dass das Spülen in einer neuen Zeile nicht einmal garantiert ist. Sie sollten daher explizit spülen, wenn das Spülen für Sie von Bedeutung ist.
Denken Sie daran, fflush(NULL);ist in der Regel eine sehr schlechte Idee. Wenn Sie viele Dateien geöffnet haben, wird die Leistung beeinträchtigt, insbesondere in einer Umgebung mit mehreren Threads, in der Sie mit allem um Sperren kämpfen.
R .. GitHub STOP HELPING ICE
14
Hinweis: Microsoft - Laufzeitbibliotheken unterstützen keine Zeile Pufferung, so printf("will print immediately to terminal"):
Schlimmer als printfim "normalen" Fall sofort zum Terminal zu gehen, ist die Tatsache, dass printfund fprintfgröber gepuffert werden, selbst in Fällen, in denen ihre Ausgabe sofort verwendet wird. Wenn MS keine Probleme behoben hat, ist es für ein Programm unmöglich, stderr und stdout von einem anderen zu erfassen und zu identifizieren, in welcher Reihenfolge die Dinge an die einzelnen Programme gesendet wurden.
Supercat
Nein, das wird nicht sofort auf dem Terminal gedruckt, es sei denn, es wurde keine Pufferung festgelegt. Standardmäßig wird die vollständige Pufferung verwendet
phuclv
12
stdout ist gepuffert und wird daher erst ausgegeben, nachdem eine neue Zeile gedruckt wurde.
"wird also erst ausgegeben, nachdem eine neue Zeile gedruckt wurde." Nicht nur das, sondern mindestens 4 weitere Fälle. Puffer voll, schreiben Sie an stderr(diese Antwort erwähnt später) fflush(stdout), fflush(NULL).
chux
11
Standardmäßig ist stdout zeilengepuffert, stderr ist nicht gepuffert und die Datei ist vollständig gepuffert.
Sie können stattdessen fprintf to stderr verwenden, das ungepuffert ist. Oder Sie können stdout spülen, wenn Sie möchten. Oder Sie können stdout auf ungepuffert setzen.
1. Kernel Buffer Cache (beschleunigt das Lesen / Schreiben)
2. Pufferung in der E / A-Bibliothek (reduziert die Anzahl der Systemaufrufe)
Nehmen wir ein Beispiel von fprintf and write().
Wenn Sie anrufen fprintf(), wird nicht direkt mit der Datei verbunden. Es geht zuerst in den Standardpuffer im Speicher des Programms. Von dort wird es mithilfe eines Schreibsystemaufrufs in den Kernel-Puffer-Cache geschrieben. Eine Möglichkeit, den E / A-Puffer zu überspringen, ist die direkte Verwendung von write (). Andere Möglichkeiten sind die Verwendung setbuff(stream,NULL). Dies setzt den Puffermodus auf keine Pufferung und Daten werden direkt in den Kernelpuffer geschrieben. Um die Daten zwangsweise in den Kernel-Puffer zu verschieben, können wir "\ n" verwenden, wodurch im Fall des Standardpuffermodus "Zeilenpufferung" der E / A-Puffer geleert wird. Oder wir können verwendenfflush(FILE *stream) .
Jetzt sind wir im Kernel-Puffer. Der Kernel (/ OS) möchte die Zugriffszeit auf die Festplatte minimieren und liest / schreibt daher nur Festplattenblöcke. Wenn also a read()ausgegeben wird, was ein Systemaufruf ist und direkt oder über aufgerufen werden kann fscanf(), liest der Kernel den Plattenblock von der Platte und speichert ihn in einem Puffer. Danach werden die Daten von hier in den Benutzerbereich kopiert.
In ähnlicher Weise werden die fprintf()aus dem E / A-Puffer empfangenen Daten vom Kernel auf die Festplatte geschrieben. Dies beschleunigt read () write ().
Um den Kernel nun zu zwingen, a zu initiieren write(), wonach die Datenübertragung von Hardware-Controllern gesteuert wird, gibt es auch einige Möglichkeiten. Wir können O_SYNCoder ähnliche Flags während Schreibaufrufen verwenden. Oder wir könnten andere Funktionen verwenden fsync(),fdatasync(),sync(), um den Kernel dazu zu bringen, Schreibvorgänge zu initiieren, sobald Daten im Kernelpuffer verfügbar sind.
setvbuf(stdout, (char*)NULL, _IONBF, 0)
umgeht das Problem, sollte aber sicherlich nicht notwendig gewesen sein. Ich verwende MSVC ++ 2008 Express. ~~~printf(..)
kein Leeren selbst durch, es ist die Pufferungstdout
, die beim Anzeigen einer neuen Zeile (wenn sie zeilengepuffert ist) möglicherweise geleert wird. Es würde genauso reagierenputchar('\n');
,printf(..)
ist also in dieser Hinsicht nicht besonders. Dies steht im Gegensatz zucout << endl;
, in dessen Dokumentation das Spülen deutlich erwähnt wird. In der Dokumentation von printf wird das Spülen überhaupt nicht erwähnt.Antworten:
Der
stdout
Stream ist standardmäßig zeilengepuffert und zeigt daher erst an, was sich im Puffer befindet, nachdem er eine neue Zeile erreicht hat (oder wenn er dazu aufgefordert wird). Sie haben einige Optionen zum sofortigen Drucken:Drucken Sie
stderr
stattdessen mitfprintf
(stderr
ist standardmäßig ungepuffert ):Spülen Sie stdout, wann immer Sie es benötigen
fflush
:Bearbeiten : Aus Andy Ross 'Kommentar unten können Sie die Pufferung auf stdout auch deaktivieren, indem Sie Folgendes verwenden
setbuf
:oder seine sichere Version
setvbuf
wie hier erklärtquelle
setbuf(stdout, NULL);
setlinebuf()
in einem Stream, der nicht auf ein Terminal gerichtet ist, am Ende jeder Zeile gespült wird.Nein, es ist kein POSIX-Verhalten, es ist ISO-Verhalten (nun, es ist POSIX-Verhalten, aber nur insoweit, als sie ISO-konform sind).
Die Standardausgabe ist zeilengepuffert, wenn erkannt werden kann, dass sie sich auf ein interaktives Gerät bezieht, andernfalls ist sie vollständig gepuffert. Es gibt also Situationen, in denen
printf
nicht gespült wird, selbst wenn eine neue Zeile gesendet werden muss, z.Dies ist aus Effizienzgründen sinnvoll, da bei der Interaktion mit einem Benutzer wahrscheinlich jede Zeile angezeigt werden soll. Wenn Sie die Ausgabe an eine Datei senden, befindet sich höchstwahrscheinlich kein Benutzer am anderen Ende (obwohl dies nicht unmöglich ist, könnte er die Datei verfolgen). Jetzt Sie könnten argumentieren, dass der Benutzer jeden Charakter sehen möchte, aber es gibt zwei Probleme damit.
Das erste ist, dass es nicht sehr effizient ist. Das zweite ist, dass das ursprüngliche ANSI C-Mandat darin bestand, in erster Linie vorhandenes Verhalten zu kodifizieren , anstatt neues Verhalten zu erfinden , und diese Entwurfsentscheidungen wurden lange vor Beginn des Prozesses durch ANSI getroffen. Selbst ISO geht heutzutage sehr vorsichtig vor, wenn bestehende Regeln in den Standards geändert werden.
Wie Sie damit umgehen sollen, wenn Sie
fflush (stdout)
nach jedem Ausgabeaufruf, den Sie sofort sehen möchten, , wird das Problem gelöst.Alternativ können Sie verwenden ,
setvbuf
vor dem Betrieb aufstdout
, um es zu ungepufferten zu setzen und Sie werden nicht über das Hinzufügen all diese Sorgen habenfflush
Linien , um Ihren Code:Denken Sie daran , dass ziemlich viel Einfluss auf die Leistung, wenn Sie sind die Ausgabe in eine Datei zu senden. Beachten Sie auch, dass die Unterstützung dafür implementierungsdefiniert ist und nicht durch den Standard garantiert wird.
Der Abschnitt ISO C99
7.19.3/3
ist das relevante Bit:quelle
myprog >/tmp/tmpfile
, ist diese vollständig gepuffert und nicht zeilengepuffert. Aus dem Speicher bleibt die Bestimmung, ob Ihre Standardausgabe interaktiv ist, der Implementierung überlassen.Das liegt wahrscheinlich an der Effizienz und daran, dass bei mehreren Programmen, die in ein einzelnes TTY schreiben, auf diese Weise keine Zeichen in einer Interlaced-Zeile angezeigt werden. Wenn also Programm A und B ausgegeben werden, erhalten Sie normalerweise:
Das stinkt, aber es ist besser als
Beachten Sie, dass das Spülen in einer neuen Zeile nicht einmal garantiert ist. Sie sollten daher explizit spülen, wenn das Spülen für Sie von Bedeutung ist.
quelle
Anruf sofort spülen
fflush(stdout)
oderfflush(NULL)
(NULL
bedeutet alles spülen).quelle
fflush(NULL);
ist in der Regel eine sehr schlechte Idee. Wenn Sie viele Dateien geöffnet haben, wird die Leistung beeinträchtigt, insbesondere in einer Umgebung mit mehreren Threads, in der Sie mit allem um Sperren kämpfen.Hinweis: Microsoft - Laufzeitbibliotheken unterstützen keine Zeile Pufferung, so
printf("will print immediately to terminal")
:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf
quelle
printf
im "normalen" Fall sofort zum Terminal zu gehen, ist die Tatsache, dassprintf
undfprintf
gröber gepuffert werden, selbst in Fällen, in denen ihre Ausgabe sofort verwendet wird. Wenn MS keine Probleme behoben hat, ist es für ein Programm unmöglich, stderr und stdout von einem anderen zu erfassen und zu identifizieren, in welcher Reihenfolge die Dinge an die einzelnen Programme gesendet wurden.stdout ist gepuffert und wird daher erst ausgegeben, nachdem eine neue Zeile gedruckt wurde.
So erhalten Sie entweder:
quelle
fflush(stdout)
.stderr
(diese Antwort erwähnt später)fflush(stdout)
,fflush(NULL)
.Standardmäßig ist stdout zeilengepuffert, stderr ist nicht gepuffert und die Datei ist vollständig gepuffert.
quelle
Sie können stattdessen fprintf to stderr verwenden, das ungepuffert ist. Oder Sie können stdout spülen, wenn Sie möchten. Oder Sie können stdout auf ungepuffert setzen.
quelle
Verwenden Sie
setbuf(stdout, NULL);
diese Option, um die Pufferung zu deaktivieren.quelle
Es gibt im Allgemeinen 2 Pufferstufen.
1. Kernel Buffer Cache (beschleunigt das Lesen / Schreiben)
2. Pufferung in der E / A-Bibliothek (reduziert die Anzahl der Systemaufrufe)
Nehmen wir ein Beispiel von
fprintf and write()
.Wenn Sie anrufen
fprintf()
, wird nicht direkt mit der Datei verbunden. Es geht zuerst in den Standardpuffer im Speicher des Programms. Von dort wird es mithilfe eines Schreibsystemaufrufs in den Kernel-Puffer-Cache geschrieben. Eine Möglichkeit, den E / A-Puffer zu überspringen, ist die direkte Verwendung von write (). Andere Möglichkeiten sind die Verwendungsetbuff(stream,NULL)
. Dies setzt den Puffermodus auf keine Pufferung und Daten werden direkt in den Kernelpuffer geschrieben. Um die Daten zwangsweise in den Kernel-Puffer zu verschieben, können wir "\ n" verwenden, wodurch im Fall des Standardpuffermodus "Zeilenpufferung" der E / A-Puffer geleert wird. Oder wir können verwendenfflush(FILE *stream)
.Jetzt sind wir im Kernel-Puffer. Der Kernel (/ OS) möchte die Zugriffszeit auf die Festplatte minimieren und liest / schreibt daher nur Festplattenblöcke. Wenn also a
read()
ausgegeben wird, was ein Systemaufruf ist und direkt oder über aufgerufen werden kannfscanf()
, liest der Kernel den Plattenblock von der Platte und speichert ihn in einem Puffer. Danach werden die Daten von hier in den Benutzerbereich kopiert.In ähnlicher Weise werden die
fprintf()
aus dem E / A-Puffer empfangenen Daten vom Kernel auf die Festplatte geschrieben. Dies beschleunigt read () write ().Um den Kernel nun zu zwingen, a zu initiieren
write()
, wonach die Datenübertragung von Hardware-Controllern gesteuert wird, gibt es auch einige Möglichkeiten. Wir könnenO_SYNC
oder ähnliche Flags während Schreibaufrufen verwenden. Oder wir könnten andere Funktionen verwendenfsync(),fdatasync(),sync()
, um den Kernel dazu zu bringen, Schreibvorgänge zu initiieren, sobald Daten im Kernelpuffer verfügbar sind.quelle