¿Qué pasa con printf () enviando salida a búfer?

Estoy revisando “C PRIMER PLUS” y hay un tema relacionado con “SALIDA DE SALIDA”. Ahora dice:

printf() sentencias printf() envían la salida a un almacenamiento intermedio llamado búfer. De vez en cuando, el material en el búfer se envía a la pantalla. Las reglas estándar de C para cuando la salida se envía desde el búfer a la pantalla son claras:

  1. Se envía cuando el búfer se llena.
  2. Cuando se encuentra un carácter de nueva línea.
  3. Cuando hay entrada inminente.

(Enviar la salida del búfer a la pantalla o al archivo se llama vaciar el búfer).

Ahora, para verificar las afirmaciones anteriores. Escribí este sencillo progtwig:

 #include int main(int argc, char** argv) { printf("Hello World"); return 0; } 

por lo tanto, ni el printf () contiene una nueva línea, ni tiene alguna entrada inminente (por ejemplo, una instrucción scanf () o cualquier otra statement de entrada). Entonces, ¿por qué imprime el contenido en la pantalla de salida?

Supongamos la primera condición validada como verdadera. El búfer se llenó (lo que no puede suceder en absoluto). Teniendo eso en cuenta, truncé la statement dentro de printf () para

 printf("Hi"); 

Todavía imprime la statement en la consola.

Entonces, ¿cuál es el problema aquí? Todas las condiciones anteriores son falsas, pero aún así obtengo el resultado en la pantalla. ¿Se puede elaborar por favor. Parece que estoy cometiendo un error al entender el concepto. Cualquier ayuda es muy apreciada.

EDITAR: según lo sugerido por un comentario muy útil, tal vez la ejecución de la función exit () después del final del progtwig está causando que todos los buffers se vacíen, dando como resultado la salida en la consola. Pero entonces si mantenemos la pantalla antes de la ejecución de exit (). Me gusta esto,

 #include int main(int argc, char** argv) { printf("Hello World!"); getchar(); return 0; } 

Todavía sale en la consola.

El buffering de salida es una técnica de optimización. Escribir datos en algunos dispositivos (discos duros fe) es una operación costosa; Es por eso que apareció el buffering. En esencia, evita escribir datos byte-by-byte (o char-by-char) y los recostack en un búfer para escribir varios KiB de datos a la vez.

Al ser una optimización, el búfer de salida debe ser transparente para el usuario (es transparente incluso para el progtwig). No debe afectar el comportamiento del progtwig; Con o sin buffering (o con diferentes tamaños de buffer), el progtwig debe comportarse igual. Para esto son las reglas que mencionaste.

Un búfer es solo un área en la memoria donde los datos a escribir se almacenan temporalmente hasta que se acumulen suficientes datos para hacer que el proceso de escritura real sea eficiente para el dispositivo. Algunos dispositivos (disco duro, etc.) ni siquiera permiten escribir (o leer) datos en partes pequeñas, sino solo en bloques de algún tamaño fijo.

Las reglas del lavado de búferes:

  1. Se envía cuando el búfer se llena.

Esto es obvio. El búfer está lleno, su propósito se cumplió, empujemos los datos hacia el dispositivo. Además, probablemente haya más datos que provengan del progtwig, necesitamos hacer espacio para él.

  1. Cuando se encuentra un carácter de nueva línea.

Hay dos tipos de dispositivos: modo de línea y modo de bloque. Esta regla se aplica solo a los dispositivos de modo de línea (el terminal, por ejemplo). No tiene mucho sentido vaciar el búfer en las líneas nuevas al escribir en el disco. Pero tiene mucho sentido hacerlo cuando el progtwig está escribiendo en el terminal. En frente del terminal está el usuario esperando impacientemente la salida. No dejes que esperen demasiado.

Pero, ¿por qué la salida a la terminal necesita buffering? Escribir en la terminal no es caro. Eso es correcto, cuando el terminal se encuentra físicamente cerca del procesador. No también cuando el terminal y el procesador están separados por la mitad del globo y el usuario ejecuta el progtwig a través de una conexión remota.

  1. Cuando hay entrada inminente.

Debería leerse “cuando haya una entrada de impedimento en el mismo dispositivo” para que quede claro.

La lectura también está protegida por la misma razón que la escritura: eficiencia. El código de lectura utiliza su propio búfer. Llena el búfer cuando es necesario, luego scanf() y las otras funciones de lectura de entrada obtienen sus datos del búfer de entrada.

Cuando una entrada está a punto de ocurrir en el mismo dispositivo, se debe vaciar el búfer (los datos realmente escritos en el dispositivo) para garantizar la coherencia. El progtwig ha enviado algunos datos a la salida y ahora espera leer los mismos datos; Es por eso que los datos se deben enviar al dispositivo para que el código de lectura lo encuentre allí y lo cargue.

Pero, ¿por qué se vacían los buffers cuando sale la aplicación?

Err … el almacenamiento en búfer es transparente, no debe afectar el comportamiento de la aplicación. Su aplicación ha enviado algunos datos a la salida. Los datos deben estar allí (en el dispositivo de salida) cuando se cierra la aplicación.

Los buffers también se vacían cuando se cierran los archivos asociados, por la misma razón. Y esto es lo que sucede cuando la aplicación sale: el código de limpieza cierra todos los archivos abiertos (la entrada y la salida estándar son solo archivos desde el punto de vista de la aplicación), el cierre obliga a vaciar los búferes.

Parte de la especificación para exit() en el estándar C (enlace POSIX dado) es:

A continuación, se vacían todos los flujos abiertos con datos no escritos en búfer, se cierran todos los flujos abiertos, …

Entonces, cuando el progtwig sale, la salida pendiente se vacía, independientemente de las nuevas líneas, etc. De manera similar, cuando el archivo está cerrado ( fclose() ), la salida pendiente se escribe:

Los datos almacenados en búfer no escritos para la transmisión se envían al entorno del host para que se escriban en el archivo; Cualquier dato almacenado no leído se descarta.

Y, por supuesto, la función fflush() la salida.

Las reglas citadas en la pregunta no son del todo exactas.

  1. Cuando el búfer está lleno, esto es correcto.

  2. Cuando se encuentra una nueva línea, esto no es correcto, aunque a menudo se aplica. Si el dispositivo de salida es un ‘dispositivo interactivo’, entonces el búfer de línea es el predeterminado. Sin embargo, si el dispositivo de salida es ‘no interactivo’ (archivo de disco, una tubería, etc.), entonces la salida no es necesariamente (o generalmente) el búfer de línea.

  3. Cuando hay una entrada inminente, esto tampoco es correcto, aunque comúnmente es la forma en que funciona. De nuevo, depende de si los dispositivos de entrada y salida son ‘interactivos’.

El modo de búfer de salida se puede modificar llamando a setvbuf() para establecer que no haya búfer, búfer de línea o búfer completo.

La norma dice (§7.21.3):

¶3 Cuando una secuencia no tiene buffer , se pretende que los caracteres aparezcan desde la fuente o en el destino tan pronto como sea posible. De lo contrario, los caracteres pueden acumularse y transmitirse hacia o desde el entorno host como un bloque. Cuando un flujo está completamente almacenado en búfer , se pretende que los caracteres se transmitan hacia o desde el entorno del host como un bloque cuando se llena un búfer. Cuando un flujo tiene un búfer de línea , se pretende que los caracteres se transmitan hacia o desde el entorno del host como un bloque cuando se encuentra un carácter de nueva línea. Además, se pretende que los caracteres se transmitan como un bloque al entorno del host cuando se llena un búfer, cuando se solicita la entrada en un flujo no almacenado, o cuando se solicita la entrada en un flujo del búfer de línea que requiere la transmisión de caracteres del 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 setbuf y setvbuf .

¶7 Al inicio del progtwig, hay tres flujos de texto predefinidos y no es necesario abrirlos explícitamente: entrada estándar (para leer la entrada convencional), salida estándar (para escribir la salida convencional) y error estándar (para escribir la salida de diagnóstico). Como se abrió inicialmente, el flujo de error estándar no está completamente almacenado en búfer; la entrada estándar y las secuencias de salida estándar están completamente almacenadas en búfer si, y solo si se puede determinar que la secuencia no se refiere a un dispositivo interactivo.

Además, §5.1.2.3 la ejecución del progtwig dice:

  • La dinámica de entrada y salida de los dispositivos interactivos se llevará a cabo como se especifica en 7.21.3. La intención de estos requisitos es que la salida sin búfer o con búfer de línea aparezca lo antes posible, para garantizar que los mensajes de solicitud aparezcan antes de que un progtwig espere la entrada.

El extraño comportamiento de printf, buffering puede explicarse con el siguiente código C simple. Lea todo el proceso de ejecución y comprensión, ya que lo siguiente no es obvio (un poco complicado)

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); while (1) { sleep (1000); } scanf("%d%d",&b,&c); a=b+c; printf("The sum is %d",a); return 1; } 

EXPERIMENTO 1:

Acción: comstackr y ejecutar el código anterior

Observaciones:

La salida esperada es

 Enter two numbers 

Pero esta salida no se ve.

EXPERIMENTO # 2:

Acción: Mueve la instrucción de Scanf arriba mientras bucle.

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); scanf("%d%d",&b,&c); while (1) { sleep (1000); } a=b+c; printf("The sum is %d",a); return 1; } 

Observaciones: Ahora se imprime la salida (razón abajo en el final) (solo por el cambio de posición de scanf)

EXPERIMENTO # 3:

Acción: Ahora agregue \ n para imprimir la statement como se muestra abajo

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers\n"); while (1) { sleep (1000); } scanf("%d%d",&b,&c); a=b+c; printf("The sum is %d",a); return 1; } 

Observación: se ve la salida Enter two numbers (después de agregar \ n)

EXPERIMENTO # 4:

Acción: Ahora elimine \ n de la línea printf, comente mientras bucle, línea scanf, línea de adición, línea printf para el resultado de impresión

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); // while (1) // { // sleep (1000); // } // scanf("%d%d",&b,&c); // a=b+c; // printf("The sum is %d",a); return 1; } 

Observaciones: La línea “Ingresar dos números” se imprime en la pantalla.

RESPONDER:

La razón detrás del extraño comportamiento se describe en el libro de Richard Stevens.

IMPRIMIR IMPRESIONES PARA PANTALLA CUANDO

El trabajo de printf es escribir la salida en el buffer stdout. el kernel vacía los buffers de salida cuando

  1. Kernel necesita leer algo desde el buffer de entrada. (EXPERIMENTO # 2)
  2. cuando encuentra una nueva línea (ya que la salida estándar está predeterminada en linebuffered) (EXPERIMENTO # 3)
  3. después de las salidas del progtwig (todos los buffers de salida se vacían) (EXPERIMENTO # 4)

De forma predeterminada, la salida estándar se establece en el búfer de línea para que printf no se imprima ya que la línea no finaliza. si no está almacenado en búfer, todas las líneas se emiten tal como están. Búfer lleno, entonces, solo cuando el búfer está lleno se vacía.