GNU/Linux >> Linux Esercitazione >  >> Linux

Perché stdout necessita di uno svuotamento esplicito quando viene reindirizzato al file?

Stai erroneamente combinando funzioni IO bufferizzate e non bufferizzate. Tale combinazione deve essere eseguita con molta attenzione soprattutto quando il codice deve essere portabile. (ed è brutto scrivere codice non portabile...)
È sicuramente meglio evitare di combinare IO con buffer e senza buffer sullo stesso descrittore di file.

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

IO senza buffer: write() , open() , close() , dup() ...

Quando usi dup2() per reindirizzare stdout. La funzione non è a conoscenza del buffer che è stato riempito da fprintf() . Quindi, quando dup2() chiude il vecchio descrittore 1 non svuota il buffer e il contenuto potrebbe essere scaricato su un output diverso. Nel tuo caso 2a è stato inviato a /dev/null .

La soluzione

Nel tuo caso è meglio usare freopen() invece di dup2() . Questo risolve tutti i tuoi problemi:

  1. Svuota i buffer del FILE originale flusso. (caso 2a)
  2. Imposta la modalità di buffering in base al file appena aperto. (caso 3)

Ecco la corretta implementazione della tua funzione:

void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}

Sfortunatamente con IO bufferizzato non puoi impostare direttamente le autorizzazioni di un file appena creato. Devi usare altre chiamate per cambiare i permessi o puoi usare estensioni glibc non portatili. Vedi il fopen() man page .


Flushing per stdout è determinato dal suo comportamento di buffering. Il buffering può essere impostato su tre modalità:_IOFBF (buffering completo:attende fino a fflush() se possibile), _IOLBF (line buffering:la nuova riga attiva lo svuotamento automatico) e _IONBF (scrittura diretta sempre usata). "Il supporto per queste caratteristiche è definito dall'implementazione e può essere influenzato dal setbuf() e setvbuf() funzioni." [C99:7.19.3.3]

"All'avvio del programma, tre flussi di testo sono predefiniti e non devono essere aperti esplicitamente:input standard (per leggere l'input convenzionale), output standard (per scrivere l'output convenzionale) e errore standard (per scrivere l'output diagnostico). Come inizialmente aperto, l'errore standard il flusso non è completamente bufferizzato; i flussi standard input e standardoutput sono completamente bufferizzati se e solo se è possibile determinare che il flusso non fa riferimento a un dispositivo interattivo." [C99:7.19.3.7]

Spiegazione del comportamento osservato

Quindi, quello che succede è che l'implementazione fa qualcosa di specifico della piattaforma per decidere se stdout verrà bufferizzato in linea. Nella maggior parte delle implementazioni di libc, questo test viene eseguito quando lo stream viene utilizzato per la prima volta.

  1. Il comportamento n. 1 è facilmente spiegabile:quando lo stream è per un dispositivo interattivo, è bufferizzato in linea e il printf() viene scaricato automaticamente.
  2. Ora è previsto anche il caso n. 2:quando reindirizziamo a un file, lo stream è completamente memorizzato nel buffer e non verrà scaricato se non con fflush() , a meno che non ci scriva un sacco di dati.
  3. Infine, comprendiamo anche il caso n. 3 per le implementazioni che eseguono il controllo sull'fd sottostante solo una volta. Perché abbiamo forzato l'inizializzazione del buffer di stdout nel primo printf() , stdout ha acquisito la modalità con buffer di riga. Quando sostituiamo fd per passare al file, è ancora bufferizzato, quindi i dati vengono scaricati automaticamente.

Alcune implementazioni effettive

Ogni libc ha libertà nel modo in cui interpreta questi requisiti, dal momento che C99 non specifica cosa sia un "dispositivo interattivo", né la voce stdio di POSIX lo estende (oltre a richiedere che stderr sia aperto per la lettura).

  1. Glibc. Vedere filedoalloc.c:L111. Qui usiamo stat() per verificare se fd è un tty e impostare la modalità di buffering di conseguenza. (Questo è chiamato da fileops.c.) stdout inizialmente ha un buffer nullo, ed è allocato al primo utilizzo del flusso in base alle caratteristiche di fd 1.

  2. Libc BSD. Codice molto simile, ma molto più pulito da seguire! Vedi questa riga in makebuf.c


Linux
  1. Perché il valore dell'inode cambia quando modifichiamo nell'editor "vi"?

  2. Perché l'utente root ha bisogno dell'autorizzazione Sudo?

  3. Perché questa pipeline di shell termina?

  4. Perché ENOENT significa No such file or directory?

  5. Perché abbiamo bisogno del file .so.1 in Linux?

Perché l'opzione Ssh -t aggiunge Cr e Lf nell'output reindirizzato?

Perché `zip` in un ciclo For funziona quando il file esiste, ma non quando non lo è?

Perché il fork del mio processo fa sì che il file venga letto all'infinito

Perché clang genera testo incomprensibile quando viene reindirizzato?

Dove vanno a finire i metadati quando salvi un file?

Perché è necessario inizializzare un dispositivo raid 10?