Perché printf non si scarica dopo la chiamata a meno che una stringa non sia nella stringa di formato?


539

Perché printfnon svuota dopo la chiamata a meno che una nuova riga non sia nella stringa di formato? Questo comportamento è POSIX? Come potrei avere printfimmediatamente flush ogni volta?


2
hai indagato se questo accade con qualsiasi file o solo con terminali? che suonerebbe come un non un terminale intelligente per uscita di linea incompleta da un programma di fondo, anche se mi aspetto che non si applicherebbe al programma in primo piano.
PypeBros,

7
Sotto Cygwin Bash vedo lo stesso comportamento errato anche se una stringa di nuova riga è nella stringa di formato. Questo problema è nuovo in Windows 7; lo stesso codice sorgente funzionava bene su Windows XP. MS cmd.exe si scarica come previsto. La correzione risolve setvbuf(stdout, (char*)NULL, _IONBF, 0)il problema, ma sicuramente non avrebbe dovuto essere necessaria. Sto usando MSVC ++ 2008 Express. ~~~
Steve Pitchers

9
Per chiarire il titolo della domanda: printf(..) non esegue alcunstdout flush di per sé, è il buffering di quello che può scaricarsi quando si vede una nuova riga (se è buffer di riga). Reagirebbe allo stesso modo putchar('\n');, quindi printf(..)non è speciale in questo senso. Ciò è in contrasto con cout << endl;la documentazione di cui menziona in modo evidente il rossore. La documentazione di printf non menziona affatto il flushing.
Evgeni Sergeev,

1
la scrittura (/ flushing) è potenzialmente un'operazione costosa, probabilmente è tamponata per motivi di prestazioni.
Hanshenrik,

@EvgeniSergeev: esiste un consenso sul fatto che la domanda abbia erroneamente diagnosticato il problema e che il flushing avvenga in presenza di una nuova riga nell'output ? (inserirne uno nella stringa di formato è un modo, ma non l'unico, di ottenerne uno nell'output).
Ben Voigt,

Risposte:


703

Lo stdoutstream è bufferizzato in linea per impostazione predefinita, quindi visualizzerà ciò che è nel buffer solo quando raggiunge una nuova riga (o quando gli viene richiesto). Hai alcune opzioni per stampare immediatamente:

Stampa stderrinvece utilizzando fprintf( nonstderr è bufferizzato per impostazione predefinita ):

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

Svuota lo stdout ogni volta che ne hai bisogno per usare fflush:

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

Modifica : dal seguente commento di Andy Ross, puoi anche disabilitare il buffering su stdout usando setbuf:

setbuf(stdout, NULL);

o la sua versione sicura setvbufcome spiegato qui

setvbuf(stdout, NULL, _IONBF, 0); 

266
Oppure, per disabilitare completamente il buffering:setbuf(stdout, NULL);
Andy Ross,

80
Inoltre, volevo solo ricordare che apparentemente in UNIX una newline in genere svuota il buffer solo se stdout è un terminale. Se l'output viene reindirizzato a un file, una nuova riga non verrà scaricata.
hora,

5
Sento che dovrei aggiungere: ho appena testato questa teoria e sto scoprendo che l'utilizzo setlinebuf()su un flusso che non è diretto a un terminale sta svuotando alla fine di ogni riga.
Doddy,

8
"Come inizialmente aperto, il flusso di errore standard non è completamente bufferizzato; i flussi di input standard e di output standard sono completamente bufferizzati se e solo se è possibile determinare il flusso in modo che non si riferisca a un dispositivo interattivo" - vedere questa domanda: stackoverflow.com / questions / 5229096 /…
Seppo Enarvi

3
@RuddZwolinski Se questa sarà una buona risposta canonica di "perché non sta stampando" sembra importante menzionare la distinzione terminale / file come da "Printf svuota sempre il buffer quando incontra una nuova riga?" direttamente in questa risposta altamente votata, rispetto alle persone che hanno bisogno di leggere i commenti ...
HostileFork dice di non fidarsi di SE

128

No, non è un comportamento POSIX, è un comportamento ISO (beh, è un comportamento POSIX ma solo nella misura in cui sono conformi a ISO).

L'output standard è bufferizzato se può essere rilevato come riferimento a un dispositivo interattivo, altrimenti è completamente bufferizzato. Quindi ci sono situazioni in cui printfnon si scarica, anche se viene inviata una nuova riga, come ad esempio:

myprog >myfile.txt

Questo ha senso per l'efficienza poiché, se stai interagendo con un utente, probabilmente vogliono vedere ogni riga. Se stai inviando l'output a un file, è molto probabile che non ci sia un utente all'altro capo (anche se non impossibile, potrebbero essere in coda al file). Ora si potrebbe sostenere che l'utente vuole vedere ogni personaggio, ma ci sono due problemi con questo.

Il primo è che non è molto efficiente. Il secondo è che il mandato originale ANSI C era di codificare principalmente il comportamento esistente , piuttosto che inventare un nuovo comportamento e che le decisioni di progettazione venivano prese molto prima che ANSI iniziasse il processo. Anche ISO oggi calpesta molto attentamente quando si modificano le norme esistenti negli standard.

Quanto a come gestirlo, se fflush (stdout)dopo ogni chiamata in uscita che si desidera vedere immediatamente, ciò risolverà il problema.

In alternativa, puoi utilizzare setvbufprima di operare stdout, per impostarlo su senza buffer e non dovrai preoccuparti di aggiungere tutte quelle fflushrighe al tuo codice:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Basta tenere a mente che può influire sulle prestazioni un po 'se si sta inviando l'output in un file. Inoltre, tieni presente che il supporto per questo è definito dall'implementazione, non garantito dallo standard.

La sezione ISO C99 7.19.3/3è il bit rilevante:

Quando uno stream è senza buffer , i personaggi devono apparire dalla sorgente o dalla destinazione il più presto possibile. Altrimenti i caratteri possono essere accumulati e trasmessi da o verso l'ambiente host come un blocco.

Quando uno stream è completamente bufferizzato , i caratteri devono essere trasmessi da o verso l'ambiente host come un blocco quando viene riempito un buffer.

Quando uno stream ha un buffer di linea , i caratteri devono essere trasmessi da o verso l'ambiente host come un blocco quando viene rilevato un carattere di nuova riga.

Inoltre, i caratteri devono essere trasmessi come blocco all'ambiente host quando viene riempito un buffer, quando viene richiesto l'input su un flusso senza buffer o quando viene richiesto l'input su un flusso con buffer di linea che richiede la trasmissione di caratteri dall'ambiente host .

Il supporto per queste caratteristiche è definito dall'implementazione e può essere influenzato dalle funzioni setbufe setvbuf.


8
Mi sono appena imbattuto in uno scenario in cui anche c'è un '\ n', printf () non scarica. È stato superato aggiungendo un fflush (stdout), come hai menzionato qui. Ma mi chiedo il motivo per cui '\ n' non è riuscito a svuotare il buffer in printf ().
Qiang Xu,

11
@QiangXu, l'output standard è bufferizzato in linea solo nel caso in cui possa essere determinato in modo definitivo per fare riferimento a un dispositivo interattivo. Quindi, ad esempio, se si reindirizza l'output con myprog >/tmp/tmpfile, questo è completamente bufferizzato anziché buffer di linea. Dalla memoria, la determinazione se l'output standard è interattivo è lasciata all'implementazione.
paxdiablo,

3
inoltre su Windows chiamando setvbuf (...., _IOLBF) non funzionerà poiché _IOLBF è uguale a _IOFBF lì: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

28

Probabilmente è così a causa dell'efficienza e perché se hai più programmi che scrivono su un singolo TTY, in questo modo non ottieni caratteri su una linea intrecciata. Quindi, se i programmi A e B sono in uscita, di solito otterrai:

program A output
program B output
program B output
program A output
program B output

Questo puzza, ma è meglio di

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Si noti che non è nemmeno garantito il flush su una nuova riga, quindi è necessario eseguire lo flush esplicito se il flushing è importante per te.


26

Svuotare immediatamente la chiamata fflush(stdout)o fflush(NULL)( NULLsignifica svuotare tutto).


31
Tieni presente che di fflush(NULL);solito è una pessima idea. Ucciderà le prestazioni se hai molti file aperti, specialmente in un ambiente multi-thread in cui dovrai combattere con tutto per i blocchi.
R .. GitHub smette di aiutare ICE il

14

Nota: le librerie di runtime Microsoft non supportano il buffering di linea, quindi printf("will print immediately to terminal"):

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


3
Peggio che printfandare immediatamente al terminale nel caso "normale" è il fatto printfe fprintfottenere un buffer più grossolano anche nei casi in cui il loro output viene immediatamente utilizzato. A meno che MS non abbia sistemato le cose, ciò rende impossibile per un programma catturare stderr e stdout da un altro e identificare in quale sequenza le cose sono state inviate a ciascuno.
supercat

no, non lo stampa immediatamente sul terminale a meno che non sia stato impostato il buffering. Per impostazione predefinita, viene utilizzato il buffering completo
phuclv il

12

stdout è bufferizzato, quindi verrà emesso solo dopo la stampa di una nuova riga.

Per ottenere un output immediato:

  1. Stampa su stderr.
  2. Crea uno stdout senza buffer.

10
Or fflush(stdout).
RastaJedi,

2
"così verrà prodotto solo dopo la stampa di una nuova riga." Non solo questo, ma almeno altri 4 casi. buffer pieno, scrittura a stderr(questa risposta menziona in seguito), fflush(stdout), fflush(NULL).
chux - Ripristina Monica il

11

per impostazione predefinita, stdout è bufferizzato in linea, stderr non è bufferizzato e il file è completamente bufferizzato.


10

Puoi invece stampare su stderr, che è senza buffer. Oppure puoi scaricare lo stdout quando vuoi. Oppure puoi impostare stdout su senza buffer.



2

Esistono generalmente 2 livelli di buffering-

1. Cache buffer del kernel (velocizza la lettura / scrittura)

2. Buffering nella libreria I / O (riduce il numero di chiamate di sistema)

Facciamo un esempio di fprintf and write().

Quando chiami fprintf(), non si reindirizza direttamente al file. Va prima al buffer stdio nella memoria del programma. Da lì viene scritto nella cache del buffer del kernel utilizzando la chiamata di sistema write. Quindi un modo per saltare il buffer I / O è usare direttamente write (). Altri modi sono usando setbuff(stream,NULL). Questo imposta la modalità di buffering su nessun buffering e i dati vengono scritti direttamente nel buffer del kernel. Per fare in modo che i dati vengano spostati sul buffer del kernel, possiamo usare "\ n", che in caso di modalità di buffer di default di "buffering di linea", svuota il buffer I / O. Oppure possiamo usare fflush(FILE *stream).

Ora siamo nel buffer del kernel. Il kernel (/ OS) vuole ridurre al minimo i tempi di accesso al disco e quindi legge / scrive solo blocchi di disco. Quindi quando read()viene emesso un a, che è una chiamata di sistema e può essere invocato direttamente o tramite fscanf(), il kernel legge il blocco del disco dal disco e lo memorizza in un buffer. Dopo che i dati vengono copiati da qui nello spazio utente.

Analogamente, i fprintf()dati ricevuti dal buffer I / O vengono scritti sul disco dal kernel. Questo rende read () write () più veloce.

Ora per forzare il kernel ad avviare a write(), dopo di che il trasferimento dei dati è controllato da controller hardware, ci sono anche alcuni modi. Possiamo usare O_SYNCflag simili o simili durante le chiamate di scrittura. Oppure potremmo usare altre funzioni come fsync(),fdatasync(),sync()fare in modo che il kernel inizi a scrivere non appena i dati sono disponibili nel buffer del kernel.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.