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

12 minutos de lectura

avatar de usuario
Yatendra Rathore

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

printf() sentencias envía la salida a un almacenamiento intermedio llamado búfer. De vez en cuando, el material del 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 una entrada inminente.

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

Ahora, para verificar las declaraciones anteriores. Escribí este programa simple:

#include<stdio.h>

int main(int argc, char** argv) {

printf("Hello World");

return 0;
}

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

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

printf("Hi");

Todavía imprime la declaración en la consola.

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

EDITAR: como lo sugiere un comentario muy útil, que tal vez la ejecución de la función exit () después del final del programa está causando que todos los búferes se vacíen, lo que da como resultado la salida en la consola. Pero luego, si mantenemos la pantalla antes de la ejecución de exit(). Me gusta esto,

#include<stdio.h>

int main(int argc, char** argv) {

printf("Hello World!");
getchar();

return 0;
}

Todavía sale en la consola.

  • Su programa está saliendo, lo más probable es que todos los búferes se vacíen en ese momento.

    – Ninja retirado

    29 de julio de 2017 a las 5:54

  • ¿Qué pasa con dos instrucciones printf() y luego un getchar() para evitar que se ejecuten la consola y la función exit()? Todavía se imprime en la consola.

    – Yatendra Rathore

    29 de julio de 2017 a las 5:55


  • Suena como un buen experimento. Háganos saber lo que encuentre.

    – Ninja retirado

    29 de julio de 2017 a las 5:56

  • El estándar C no prescribe cómo funciona el almacenamiento en búfer. Las implementaciones pueden y se comportan de manera diferente. Algunas bibliotecas estándar de C que he visto eligen vaciar automáticamente cuando stdout está conectado a la consola y hay una nueva línea en la salida o el programa se bloquea en stdin. Ese es probablemente el comportamiento que estás viendo aquí. Por otro lado stderr a menudo no tiene búfer (o tiene un búfer de 1 carácter).

    – Gen

    29 de julio de 2017 a las 6:07


  • Pruebe con un printf sin una nueva línea, luego con un sueño (10) y finalmente con un printf con una nueva línea.

    – Steve cumbre

    29 de julio de 2017 a las 6:08


avatar de usuario
axiac

El almacenamiento en búfer de salida es una técnica de optimización. Escribir datos en algunos dispositivos (discos duros, por ejemplo) es una operación costosa; por eso apareció el almacenamiento en búfer. En esencia, evita escribir datos byte por byte (o carácter por carácter) y los recopila en un búfer para escribir varios KiB de datos a la vez.

Al ser una optimización, el almacenamiento en búfer de salida debe ser transparente para el usuario (es transparente incluso para el programa). No debe afectar el comportamiento del programa; con o sin almacenamiento en búfer (o con diferentes tamaños de búfer), el programa debe comportarse de la misma manera. Para eso están las normas que mencionas.

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

Las reglas del lavado de búfer:

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

Esto es obvio. El búfer está lleno, se cumplió su propósito, empujemos los datos hacia el dispositivo. Además, probablemente haya más datos por venir del programa, necesitamos hacer espacio para ellos.

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

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

Pero, ¿por qué la salida a la terminal necesita almacenamiento en búfer? Escribir en el terminal no es caro. Eso es correcto, cuando el terminal está ubicado físicamente cerca del procesador. Tampoco cuando el terminal y el procesador están separados por medio globo terráqueo y el usuario ejecuta el programa a través de una conexión remota.

  1. Cuando hay una entrada inminente.

Debe decir “cuando hay una entrada que impide en el mismo dispositivo” para que quede claro.

La lectura también se amortigua 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 entonces 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, el búfer debe vaciarse (los datos realmente escritos en el dispositivo) para garantizar la coherencia. El programa ha enviado algunos datos a la salida y ahora espera volver a leer los mismos datos; es por eso que los datos deben descargarse en el dispositivo para que el código de lectura los encuentre allí y los cargue.

Pero, ¿por qué se vacían los búferes cuando se cierra 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 búferes 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 salida estándar son solo archivos desde el punto de vista de la aplicación), las fuerzas de cierre vacían los búferes.

  • Agregaría que no es necesario implementar el búfer; por ejemplo, las implementaciones de uC generalmente no se implementan búfer.

    – 0___________

    8 de agosto de 2018 a las 22:26

avatar de usuario
jonathan leffler

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 almacenados en búfer no escritos, se cierran todos los flujos abiertos,…

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

Todos 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; todos los datos almacenados en el búfer no leídos se descartan.

Y, por supuesto, el fflush() La función vacía la salida.

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

  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’, 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 necesariamente (o generalmente) tiene un búfer de línea.

  3. Cuando hay una entrada inminente, esto tampoco es correcto, aunque normalmente es así como funciona. Nuevamente, depende de si los dispositivos de entrada y salida son ‘interactivos’.

El modo de almacenamiento en búfer de salida se puede modificar llamando setvbuf()

para configurar sin almacenamiento en búfer, almacenamiento en búfer de línea o almacenamiento en búfer completo.

La norma dice (§7.21.3):

¶3 Cuando una corriente es sin búfer, los caracteres están destinados a 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 anfitrión como un bloque. Cuando una corriente es totalmente almacenado en búfer, los caracteres están destinados a transmitirse hacia o desde el entorno del host como un bloque cuando se llena un búfer. Cuando una corriente es línea almacenada en búfer, los caracteres están destinados a transmitirse hacia o desde el entorno del host como un bloque cuando se encuentra un carácter de nueva línea. Además, los caracteres están destinados a transmitirse como un bloque al entorno del host cuando se llena un búfer, cuando se solicita una entrada en un flujo sin búfer o cuando se solicita una 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 la setbuf y setvbuf funciones

¶7 Al iniciar el programa, hay tres flujos de texto predefinidos y no es necesario abrirlos explícitamente: entrada estándar (para leer entrada convencional), salida estándar (para escribir salida convencional), y error estándar (para escribir la salida de diagnóstico). Tal como se abrió inicialmente, el flujo de error estándar no está totalmente almacenado en el búfer; los flujos de entrada estándar y de salida estándar se almacenan en búfer completos si y solo si se puede determinar que el flujo no hace referencia a un dispositivo interactivo.

Además, §5.1.2.3 Ejecución del programa 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 aviso realmente aparezcan antes de que un programa espere la entrada.

avatar de usuario
Sudhakar MNSR

El extraño comportamiento de printf, el almacenamiento en búfer se puede explicar con el siguiente código C simple. lea todo, ejecute y comprenda, ya que lo siguiente no es obvio (un poco complicado)

#include <stdio.h>
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: compilar y ejecutar el código anterior

Observaciones:

La salida esperada es

Enter two numbers

Pero esta salida no se ve.

EXPERIMENTO #2:

Acción: Mueva la instrucción Scanf arriba del ciclo while.

#include <stdio.h>
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 (motivo a continuación al final) (solo por cambio de posición de escaneo)

EXPERIMENTO #3:

Acción: ahora agregue \n para imprimir la declaración como se muestra a continuación

#include <stdio.h>
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: La salida Enter two numbers se ve (después de agregar \n)

EXPERIMENTO #4:

Acción: ahora elimine \n de la línea printf, comente el bucle while, la línea scanf, la línea de adición, la línea printf para imprimir el resultado

#include <stdio.h>
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: Se imprime en pantalla la línea “Ingrese dos números”.

RESPONDER:

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

PRINTF IMPRIME EN LA PANTALLA CUANDO

El trabajo de printf es escribir la salida en el búfer de salida estándar. kernel vacía los búferes de salida cuando

  1. kernel necesita leer algo desde el búfer de entrada. (EXPERIMENTO #2)
  2. cuando encuentra una nueva línea (ya que stdout está configurado de forma predeterminada en linebuffered)(EXPERIMENTO #3)
  3. después de que el programa sale (todos los búferes de salida se vacían) (EXPERIMENTO #4)

De forma predeterminada, stdout se establece en el almacenamiento en búfer de línea, por lo que printf no se imprimirá porque la línea no terminó. si no está almacenado en búfer, todas las líneas se envían tal como están. Buffer completo entonces, solo cuando el buffer está lleno se vacía.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad