Sie kombinieren fälschlicherweise gepufferte und ungepufferte IO-Funktionen. Eine solche Kombination muss sehr sorgfältig durchgeführt werden, insbesondere wenn der Code portabel sein muss. (und es ist schlecht, nicht portierbaren Code zu schreiben ...)
Es ist sicherlich am besten zu vermeiden, gepufferte und ungepufferte IO auf demselben Dateideskriptor zu kombinieren.
Gepufferte E/A: fprintf()
, fopen()
, fclose()
, freopen()
...
Unbuffered IO: write()
, open()
, close()
, dup()
...
Wenn Sie dup2()
verwenden um stdout umzuleiten. Der Funktion ist der von fprintf()
gefüllte Puffer nicht bekannt . Also wenn dup2()
schließt den alten Deskriptor 1, es löscht den Puffer nicht und der Inhalt könnte in eine andere Ausgabe geräumt werden. In Ihrem Fall 2a wurde es an /dev/null
gesendet .
Die Lösung
In Ihrem Fall verwenden Sie am besten freopen()
statt dup2()
. Dies löst alle Ihre Probleme:
- Es leert die Puffer des ursprünglichen
FILE
Strom. (Fall 2a) - Es stellt den Puffermodus entsprechend der neu geöffneten Datei ein. (Fall 3)
Hier ist die korrekte Implementierung Ihrer Funktion:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
Leider können Sie mit gepuffertem IO die Berechtigungen einer neu erstellten Datei nicht direkt festlegen. Sie müssen andere Aufrufe verwenden, um die Berechtigungen zu ändern, oder Sie können nicht portierbare glibc-Erweiterungen verwenden. Siehe fopen() man page
.
Spülen für stdout
wird durch sein Pufferverhalten bestimmt. Die Pufferung kann auf drei Modi eingestellt werden:_IOFBF
(volle Pufferung:wartet bis fflush()
wenn möglich), _IOLBF
(Zeilenpufferung:Zeilenumbruch löst automatisches Leeren aus) und _IONBF
(direktes Schreiben wird immer verwendet). „Die Unterstützung dieser Eigenschaften ist implementierungsabhängig und kann über setbuf()
beeinflusst werden und setvbuf()
Funktionen." [C99:7.19.3.3]
„Beim Programmstart sind drei Textströme vordefiniert und müssen nicht explizit geöffnet werden – Standardeingabe (zum Lesen der konventionellen Eingabe), Standardausgabe (zum Schreiben der konventionellen Ausgabe) und Standardfehler (zum Schreiben der Diagnoseausgabe). Stream nicht vollständig gepuffert ist; die Standardeingabe- und Standardausgabestreams werden vollständig gepuffert, wenn und nur wenn festgestellt werden kann, dass der Stream nicht auf ein interaktives Gerät verweist." [C99:7.19.3.7]
Erklärung des beobachteten Verhaltens
Was also passiert, ist, dass die Implementierung etwas Plattformspezifisches tut, um zu entscheiden, ob stdout
wird zeilengepuffert. In den meisten libc-Implementierungen wird dieser Test durchgeführt, wenn der Stream zum ersten Mal verwendet wird.
- Verhalten Nr. 1 lässt sich leicht erklären:Wenn der Stream für ein interaktives Gerät bestimmt ist, wird er zeilengepuffert und der
printf()
wird automatisch gespült. - Fall #2 wird jetzt auch erwartet:Wenn wir zu einer Datei umleiten, ist der Stream vollständig gepuffert und wird nicht geleert, außer mit
fflush()
, es sei denn, Sie schreiben Unmengen von Daten darauf. - Schließlich verstehen wir Fall #3 auch für Implementierungen, die die Überprüfung des zugrunde liegenden fd nur einmal durchführen. Weil wir die Initialisierung des Puffers von stdout im ersten
printf()
erzwungen haben , stdout hat den zeilengepufferten Modus übernommen. Wenn wir das fd auslagern, um in die Datei zu gehen, ist es immer noch zeilengepuffert, sodass die Daten automatisch geleert werden.
Einige aktuelle Implementierungen
Jede libc hat Spielraum, wie sie diese Anforderungen interpretiert, da C99 weder spezifiziert, was ein "interaktives Gerät" ist, noch erweitert der stdio-Eintrag von POSIX dies (außer dass stderr zum Lesen geöffnet sein muss).
-
Glibc. Siehe filedoalloc.c:L111. Hier verwenden wir
stat()
um zu testen, ob fd ein tty ist, und stellen Sie den Puffermodus entsprechend ein. (Dies wird von fileops.c aufgerufen.)stdout
hat zunächst einen Nullpuffer und wird bei der ersten Verwendung des Streams basierend auf den Eigenschaften von fd 1 zugewiesen. -
BSD-libc. Sehr ähnlicher, aber viel saubererer Code folgt! Siehe diese Zeile in makebuf.c