read - stdout c




Warum benötigt stdout explizites Leeren, wenn es in Datei umgeleitet wird? (2)

Das Flushing für stdout wird durch sein Pufferverhalten bestimmt. Die Pufferung kann auf drei Modi eingestellt werden: _IOFBF (vollständige Pufferung: wartet bis fflush() wenn möglich), _IOLBF (Zeilenpufferung: Newline löst automatisches Flushing) und _IONBF (direktes Schreiben wird immer verwendet). "Die Unterstützung für diese Merkmale ist implementationsdefiniert und kann über die Funktionen setbuf() und setvbuf() werden." [C99: 7.19.3.3]

"Beim Programmstart sind drei Text-Streams vordefiniert und müssen nicht explizit geöffnet werden - Standard-Eingang (zum Lesen eines konventionellen Eingangs), Standard-Ausgang (zum Schreiben eines konventionellen Ausgangs) und Standard-Fehler (zum Schreiben eines Diagnose-Ausgangs) Standard-Fehler-Stream ist nicht vollständig gepuffert, Standard-Input- und Standard-Output-Streams sind 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 passiert ist also, dass die Implementierung plattformspezifisch ist, um zu entscheiden, ob stdout liniengepuffert werden soll. In den meisten libc-Implementierungen wird dieser Test durchgeführt, wenn der Stream zum ersten Mal verwendet wird.

  1. Verhalten # 1 ist leicht zu erklären: Wenn der Stream für ein interaktives Gerät ist, wird er zwischengespeichert, und printf() wird automatisch geleert.
  2. Fall # 2 wird jetzt auch erwartet: Wenn wir in eine Datei umleiten, wird der Stream vollständig gepuffert und wird nur mit fflush() , es sei denn, Sie schreiben Daten in die Datei.
  3. Schließlich verstehen wir auch Fall Nr. 3 für Implementierungen, die die Überprüfung des zugrundeliegenden fd nur einmal durchführen. Da der stdout-Puffer im ersten printf() initialisiert wurde, hat stdout den Zeilenpuffermodus übernommen. Wenn wir das fd austauschen, um zur Datei zu gehen, ist es immer noch zwischengespeichert, so dass die Daten automatisch gelöscht werden.

Einige tatsächliche Implementierungen

Jede libc hat einen Spielraum bei der Interpretation dieser Anforderungen, da C99 nicht angibt, was ein "interaktives Gerät" ist, und auch nicht der POSIX-STDIO-Eintrag dies erweitert (abgesehen davon, dass stderr zum Lesen geöffnet sein muss).

  1. Glibc. Siehe filedoalloc.c:L111 . Hier verwenden wir stat() , um zu testen, ob fd ein tty ist, und setzen den Puffermodus entsprechend. (Dies wird von fileops.c aufgerufen.) Stdout hat anfangs einen Null-Puffer und wird bei der ersten Verwendung des Streams basierend auf den Eigenschaften von fd 1 zugewiesen.

  2. BSD libc. Sehr ähnlich, aber viel sauberer Code zu folgen! Siehe diese Zeile in makebuf.c

Das Verhalten von printf() scheint von der Position von stdout abhängig zu sein.

  1. Wenn stdout an die Konsole gesendet wird, wird printf() zeilengepuffert und nach dem Drucken einer neuen Zeile gelöscht.
  2. Wenn stdout in eine Datei umgeleitet wird, wird der Puffer erst fflush() wenn fflush() aufgerufen wird.
  3. Wenn printf() wird, bevor stdout in Datei umgeleitet wird, werden nachfolgende Schreibvorgänge (in die Datei) liniengepuffert und nach newline gelöscht.

Wann wird die stdout Leitung gepuffert und wann muss fflush() aufgerufen werden?

Minimales Beispiel von jedem:

void RedirectStdout2File(const char* log_path) {
    int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
    dup2(fd,STDOUT_FILENO);
    if (fd != STDOUT_FILENO) close(fd);
}

int main_1(int argc, char* argv[]) {
    /* Case 1: stdout is line-buffered when run from console */
    printf("No redirect; printed immediately\n");
    sleep(10);
}

int main_2a(int argc, char* argv[]) {
    /* Case 2a: stdout is not line-buffered when redirected to file */
    RedirectStdout2File(argv[0]);
    printf("Will not go to file!\n");
    RedirectStdout2File("/dev/null");
}
int main_2b(int argc, char* argv[]) {
    /* Case 2b: flushing stdout does send output to file */
    RedirectStdout2File(argv[0]);
    printf("Will go to file if flushed\n");
    fflush(stdout);
    RedirectStdout2File("/dev/null");
}

int main_3(int argc, char* argv[]) {
    /* Case 3: printf before redirect; printf is line-buffered after */
    printf("Before redirect\n");
    RedirectStdout2File(argv[0]);
    printf("Does go to file!\n");
    RedirectStdout2File("/dev/null");
}

Sie kombinieren falsch 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, unportablen Code zu schreiben ...)
Es ist sicherlich am besten zu vermeiden, gepufferte und ungepufferte IO für den gleichen Dateideskriptor zu kombinieren.

fopen() fclose() : fprintf() , fopen() , fclose() , freopen() ...

Unbuffered IO: write() , open() , close() , dup() ...

Wenn Sie dup2() , um stdout umzuleiten. Die Funktion kennt nicht den Puffer, der von fprintf() gefüllt wurde. Wenn also dup2() den alten Deskriptor 1 schließt, wird der Puffer nicht dup2() und der Inhalt könnte zu einer anderen Ausgabe dup2() werden. In Ihrem Fall 2a wurde es an /dev/null gesendet.

Die Lösung

In Ihrem Fall ist es am besten, freopen() anstelle von dup2() . Dies löst alle Ihre Probleme:

  1. Es löscht die Puffer des ursprünglichen FILE Streams. (Fall 2a)
  2. Er legt den Puffermodus entsprechend der neu geöffneten Datei fest. (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 bei gepufferter E / A 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 die fopen() man page .







stdout