¿Por qué printf no se vacía después de la llamada a menos que haya una nueva línea en la cadena de formato?

539

¿Por qué no printfse descarga después de la llamada a menos que haya una nueva línea en la cadena de formato? ¿Es este comportamiento POSIX? ¿Cómo podría tener printfuna descarga inmediata cada vez?

Loco chenz
fuente
2
¿investigó si esto sucede con algún archivo o solo con terminales? que sonaría a ser una característica de terminales inteligentes como para no salida de línea sin terminar de un programa en segundo plano, aunque espero que no se aplicaría a la programa en primer plano.
PypeBros
77
Bajo Cygwin bash, veo este mismo mal comportamiento incluso si una nueva línea está en la cadena de formato. Este problema es nuevo en Windows 7; el mismo código fuente funcionó bien en Windows XP. MS cmd.exe se descarga como se esperaba. La solución soluciona setvbuf(stdout, (char*)NULL, _IONBF, 0)el problema, pero seguramente no debería haber sido necesario. Estoy usando MSVC ++ 2008 Express. ~~~
Steve Pitchers
99
Para aclarar el título de la pregunta: printf(..) no se enjuaga por sí mismo, es el almacenamiento en búfer lo stdoutque puede enjuagarse al ver una nueva línea (si está en línea). Reaccionaría de la misma manera putchar('\n');, por printf(..)lo que no es especial a este respecto. Esto está en contraste con cout << endl;, cuya documentación menciona prominentemente el enrojecimiento. La documentación de printf no menciona el lavado en absoluto.
Evgeni Sergeev
1
la escritura (/ flushing) es una operación potencialmente costosa, probablemente esté protegida por razones de rendimiento.
hanshenrik
@EvgeniSergeev: ¿Existe un consenso de que la pregunta ha diagnosticado incorrectamente el problema y que el enrojecimiento ocurre cuando hay una nueva línea en la salida ? (poner uno en la cadena de formato es una forma, pero no la única, de obtener uno en la salida).
Ben Voigt

Respuestas:

703

los stdout secuencia está almacenada en línea de forma predeterminada, por lo que solo mostrará lo que está en el búfer después de que llegue a una nueva línea (o cuando se le indique). Tiene algunas opciones para imprimir de inmediato:

Imprimir en su stderrlugar utilizando fprintf(no stderrestá protegido de forma predeterminada ):

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

Vaciar stdout siempre que lo necesite para usar fflush:

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

Editar : a partir del comentario de Andy Ross a continuación, también puede deshabilitar el almacenamiento en búfer en stdout usando setbuf:

setbuf(stdout, NULL);

o su versión segura setvbufcomo se explica aquí

setvbuf(stdout, NULL, _IONBF, 0); 
Rudd Zwolinski
fuente
266
O, para desactivar el almacenamiento en búfer por completo:setbuf(stdout, NULL);
Andy Ross
80
Además, solo quería mencionar que, aparentemente, en UNIX una nueva línea generalmente solo vaciará el búfer si stdout es un terminal. Si la salida se redirige a un archivo, una nueva línea no se vaciará.
hora
55
Siento que debo agregar: acabo de probar esta teoría y descubro que usar setlinebuf()un flujo que no está dirigido a un terminal se descarga al final de cada línea.
Doddy
8
"Como se abrió inicialmente, el flujo de error estándar no está completamente protegido; los flujos de entrada y salida estándar están totalmente protegidos si y solo si se puede determinar que el flujo no se refiere a un dispositivo interactivo" - vea esta pregunta: stackoverflow.com / preguntas / 5229096 /…
Seppo Enarvi
3
@RuddZwolinski Si esta va a ser una buena respuesta canónica de "¿por qué no está imprimiendo?", Parece importante mencionar la distinción terminal / archivo según "¿Printf siempre vacía el búfer al encontrar una nueva línea?" directamente en esta respuesta altamente votada, frente a las personas que necesitan leer los comentarios ...
HostileFork dice que no confíes en SE
128

No, no es un comportamiento POSIX, es un comportamiento ISO (bueno, es un comportamiento POSIX pero solo en la medida en que se ajustan a ISO).

La salida estándar se almacena en línea si se puede detectar para referirse a un dispositivo interactivo, de lo contrario, está completamente protegida. Por lo tanto, hay situaciones en las printfque no se vaciará, incluso si recibe una nueva línea para enviar, como:

myprog >myfile.txt

Esto tiene sentido para la eficiencia ya que, si está interactuando con un usuario, probablemente quiera ver cada línea. Si está enviando la salida a un archivo, lo más probable es que no haya un usuario en el otro extremo (aunque no es imposible, podrían estar siguiendo el archivo). Ahora podrías argumentar que el usuario quiere ver cada personaje, pero hay dos problemas con eso.

El primero es que no es muy eficiente. El segundo es que el mandato original de ANSI C era codificar principalmente el comportamiento existente , en lugar de inventar un nuevo comportamiento, y esas decisiones de diseño se tomaron mucho antes de que ANSI comenzara el proceso. Incluso ISO hoy en día pisa con mucho cuidado al cambiar las reglas existentes en los estándares.

En cuanto a cómo lidiar con eso, si fflush (stdout)después de cada llamada de salida que desea ver de inmediato, eso resolverá el problema.

Alternativamente, puede usarlo setvbufantes de operar stdout, para configurarlo como sin búfer y no tendrá que preocuparse por agregar todas esas fflushlíneas a su código:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Solo tenga en cuenta que puede afectar bastante el rendimiento si está enviando la salida a un archivo. También tenga en cuenta que el soporte para esto está definido por la implementación, no garantizado por el estándar.

La sección ISO C99 7.19.3/3es el bit relevante:

Cuando una secuencia no tiene búfer , los caracteres deben aparecer desde el origen o en el destino lo antes posible. De lo contrario, los caracteres pueden acumularse y transmitirse hacia o desde el entorno host como un bloque.

Cuando una secuencia está completamente almacenada en búfer , los caracteres están destinados a ser transmitidos hacia o desde el entorno host como un bloque cuando se llena un búfer.

Cuando una secuencia tiene un buffer de línea , los caracteres están destinados a ser transmitidos hacia o desde el entorno host como un bloque cuando se encuentra un carácter de nueva línea.

Además, los caracteres están destinados a ser transmitidos como un bloque al entorno del host cuando se llena un búfer, cuando se solicita la entrada en un flujo sin búfer, o cuando se solicita la entrada en un flujo con búfer de línea que requiere la transmisión de caracteres desde el entorno del host .

El soporte para estas características está definido por la implementación y puede verse afectado a través de las funciones setbufy setvbuf.

paxdiablo
fuente
8
Acabo de encontrar un escenario donde incluso hay un '\ n', printf () no funciona. Se superó agregando una descarga (stdout), como mencionó aquí. Pero me pregunto la razón por la cual '\ n' no pudo vaciar el búfer en printf ().
Qiang Xu
11
@QiangXu, la salida estándar se almacena en línea solo en el caso en que se pueda determinar definitivamente que se refiere a un dispositivo interactivo. Entonces, por ejemplo, si redirige la salida con myprog >/tmp/tmpfile, está completamente almacenado en lugar de estar en línea. Desde la memoria, la determinación de si su salida estándar es interactiva se deja a la implementación.
paxdiablo
3
Además, en Windows, llamar a setvbuf (...., _IOLBF) no funcionará ya que _IOLBF es lo mismo que _IOFBF allí: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz
28

Probablemente sea así debido a la eficiencia y porque si tiene múltiples programas escribiendo en un solo TTY, de esta manera no obtendrá caracteres entrelazados en una línea. Entonces, si los programas A y B están generando, generalmente obtendrá:

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

Esto apesta, pero es mejor que

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Tenga en cuenta que ni siquiera se garantiza que se vacíe en una nueva línea, por lo que debe hacerlo explícitamente si el lavado le importa.

Hospitalidad del sur
fuente
26

Para enjuagar inmediatamente la llamada fflush(stdout)o fflush(NULL)( NULLsignifica enjuagar todo).

Aaron
fuente
31
Tenga en cuenta fflush(NULL);que generalmente es una muy mala idea. Eliminará el rendimiento si tiene muchos archivos abiertos, especialmente en un entorno de subprocesos múltiples donde luchará con todo por bloqueos.
R .. GitHub DEJA DE AYUDAR AL HIELO
14

Nota: las bibliotecas de tiempo de ejecución de Microsoft no admiten el almacenamiento en búfer de línea, por lo tanto printf("will print immediately to terminal"):

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

Renato
fuente
3
Peor que printfir de inmediato a la terminal en el caso "normal" es el hecho de que printfy fprintfconseguir más grueso tamponada incluso en los casos en que su producción se puso en práctica de inmediato. A menos que MS haya arreglado las cosas, eso hace imposible que un programa capture stderr y stdout de otro e identifique en qué secuencia se enviaron las cosas a cada uno.
supercat
no, no imprime eso inmediatamente en el terminal a menos que no se haya configurado el almacenamiento en búfer. Por defecto se utiliza el almacenamiento en búfer completo
phuclv
12

stdout se almacena en búfer, por lo que solo se generará una vez que se imprima una nueva línea.

Para obtener resultados inmediatos, ya sea:

  1. Imprimir en stderr.
  2. Hacer stdout sin búfer.
Douglas Leeder
fuente
10
O fflush(stdout).
RastaJedi
2
"por lo que solo saldrá después de que se imprima una nueva línea". No solo esto, sino al menos otros 4 casos. búfer lleno, escribir en stderr(esta respuesta se menciona más adelante) fflush(stdout),, fflush(NULL).
chux - Restablece a Monica el
11

de manera predeterminada, stdout tiene buffer de línea, stderr no tiene buffer y el archivo está completamente protegido.

woso
fuente
10

Puede fprintf a stderr, que no tiene búfer, en su lugar. O puede descargar stdout cuando lo desee. O puede establecer stdout como sin búfer.

Rasmus Kaj
fuente
10

Se usa setbuf(stdout, NULL);para deshabilitar el almacenamiento en búfer.

dnahc araknayirp
fuente
2

Generalmente hay 2 niveles de almacenamiento en búfer.

1. Caché de memoria intermedia del núcleo (hace que la lectura / escritura sea más rápida)

2. Almacenamiento en búfer en la biblioteca de E / S (reduce el número de llamadas al sistema)

Tomemos ejemplo de fprintf and write().

Cuando llamas fprintf(), no aparece directamente en el archivo. Primero va al búfer stdio en la memoria del programa. A partir de ahí, se escribe en la memoria caché del búfer del kernel utilizando la llamada al sistema de escritura. Entonces, una forma de omitir el búfer de E / S es directamente mediante write (). Otras formas son mediante el uso setbuff(stream,NULL). Esto establece el modo de almacenamiento en búfer en ningún almacenamiento en búfer y los datos se escriben directamente en el búfer del núcleo. Para hacer que los datos se transfieran a la fuerza al búfer del núcleo, podemos usar "\ n", que en el caso del modo de búfer predeterminado de 'búfer de línea', vaciará el búfer de E / S. O podemos usar fflush(FILE *stream).

Ahora estamos en el buffer del núcleo. Kernel (/ OS) quiere minimizar el tiempo de acceso al disco y, por lo tanto, solo lee / escribe bloques de disco. Entonces, cuando read()se emite un, que es una llamada al sistema y se puede invocar directamente o mediante fscanf(), el núcleo lee el bloque de disco del disco y lo almacena en un búfer. Después de eso, los datos se copian desde aquí al espacio de usuario.

Del mismo modo, fprintf()el núcleo escribe los datos recibidos del búfer de E / S en el disco. Esto hace que read () write () sea más rápido.

Ahora, para forzar al núcleo a iniciar un write(), después de lo cual la transferencia de datos es controlada por los controladores de hardware, también hay algunas formas. Podemos usar O_SYNCbanderas o similares durante las llamadas de escritura. O podríamos usar otras funciones como fsync(),fdatasync(),sync()hacer que el núcleo inicie escrituras tan pronto como los datos estén disponibles en el búfer del núcleo.

o_O
fuente