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?

Verrückter Chenz
quelle
2
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:

Drucken Sie stderrstattdessen mit fprintf( stderrist standardmäßig ungepuffert ):

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

Spülen Sie stdout, wann immer Sie es benötigen fflush:

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

Bearbeiten : Aus Andy Ross 'Kommentar unten können Sie die Pufferung auf stdout auch deaktivieren, indem Sie Folgendes verwenden setbuf:

setbuf(stdout, NULL);

oder seine sichere Version setvbufwie hier erklärt

setvbuf(stdout, NULL, _IONBF, 0); 
Rudd Zwolinski
quelle
266
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.

paxdiablo
quelle
8
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.
Paxdiablo
3
Unter Windows funktioniert der Aufruf von setvbuf (...., _IOLBF) nicht, da _IOLBF mit _IOFBF identisch ist: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz
28

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.

Gastfreundschaft aus dem Süden
quelle
26

Anruf sofort spülen fflush(stdout)oder fflush(NULL)( NULLbedeutet alles spülen).

Aaron
quelle
31
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"):

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

Renato
quelle
3
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.

So erhalten Sie entweder:

  1. Drucken auf stderr.
  2. Machen Sie stdout ungepuffert.
Douglas Leeder
quelle
10
Oder fflush(stdout).
RastaJedi
2
"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.

woso
quelle
10

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.

Rasmus Kaj
quelle
10

Verwenden Sie setbuf(stdout, NULL);diese Option, um die Pufferung zu deaktivieren.

dnahc araknayirp
quelle
2

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 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.

o_O
quelle