Archivos (9): buffers

El funcionamiento interno de los archivos y steams en C provoca que a veces puedan ocurrir errores sutiles. En este vídeo explico el funcionamiento de un buffer para que sepamos cómo controlar la forma en la que se vuelca contenido en un archivo.

Cuando trabajamos con C, es común encontrarnos con comportamientos extraños en la entrada y salida de datos, especialmente cuando usamos funciones como printf o scanf. A veces, parece que la salida no aparece en pantalla inmediatamente, o que la lectura desde teclado no funciona como esperamos. Esto se debe a un concepto fundamental que está detrás de la gestión de archivos y dispositivos en C: los buffers de entrada/salida.

Un buffer es una zona de memoria temporal que el sistema operativo utiliza para almacenar datos antes de escribirlos en disco o mostrarlos en pantalla. La razón principal de su existencia es la eficiencia. Escribir directamente en disco o en pantalla puede ser lento, porque el hardware no es tan rápido como la memoria. Además, en un sistema multitarea, muchos procesos compiten por acceder a estos recursos, por lo que el sistema operativo debe gestionar el acceso para evitar conflictos y optimizar el rendimiento.

Cuando hacemos una llamada para escribir datos, como fwrite o fprintf, el sistema no escribe inmediatamente en el dispositivo o archivo. En lugar de eso, copia los datos al buffer asociado al descriptor de archivo. Solo cuando este buffer se llena, o cuando ocurre un evento específico, el sistema vacía el buffer y escribe todo de golpe. Esto es parecido a cuando llevamos varios platos a la cocina de una sola vez en lugar de ir uno por uno.

En el caso de la salida estándar, como con printf, el buffer suele ser de tipo linebuffered. Esto significa que el contenido se envía a la pantalla cuando se encuentra un salto de línea (\n). Por eso, si hacemos un printf sin salto de línea, puede que no veamos nada hasta que el buffer se vacíe. En cambio, la entrada estándar también usa un buffer que espera a que presionemos Enter para procesar la línea completa.

Para controlar cuándo se vacía el buffer, podemos usar la función fflush. Esta función fuerza a que el buffer asociado a un archivo o dispositivo se vacíe inmediatamente, mostrando o guardando los datos pendientes. Por ejemplo, si hacemos un printf sin salto de línea y luego un fflush(stdout), veremos la salida en pantalla sin esperar.

Si queremos un control más fino sobre el buffering, podemos usar la función setvbuf. Esta función nos permite definir el buffer que queremos usar para un archivo o dispositivo, así como el modo de buffering. Tiene cuatro parámetros: el descriptor de archivo (FILE*), un puntero a un buffer que podemos crear nosotros, el modo de buffering y el tamaño del buffer.

Los modos de buffering que podemos elegir son tres:

  • _IOFBF: buffer completo. Los datos se almacenan hasta que el buffer se llena y entonces se vacía.
  • _IOLBF: buffer por línea. Los datos se vacían cuando se encuentra un salto de línea.
  • _IONBF: sin buffer. Los datos se escriben inmediatamente, sin almacenamiento intermedio.

Por ejemplo, si definimos un buffer de 200 bytes para stdout con _IOFBF, los datos no aparecerán en pantalla hasta que hayamos escrito esos 200 bytes o llamemos a fflush. Esto nos permite optimizar la salida si sabemos que vamos a escribir grandes cantidades de datos.

Si no queremos preocuparnos por crear nuestro propio buffer, podemos pasar NULL como segundo parámetro en setvbuf. En ese caso, el sistema asignará un buffer automáticamente, generalmente de tamaño BUFSIZ, que suele ser 4 KB u 8 KB, dependiendo del sistema operativo.

Existe también la función setbuf, que es una versión simplificada de setvbuf. Con setbuf podemos activar o desactivar el buffering pasando un buffer o NULL. Sin embargo, hay que tener cuidado con el tamaño del buffer que pasamos, porque setbuf asume que el buffer tiene tamaño BUFSIZ. Si le pasamos un buffer más pequeño, podemos provocar errores o vulnerabilidades.

En resumen, entender cómo funcionan los buffers en C nos ayuda a evitar sorpresas con la entrada y salida de datos. Podemos controlar cuándo se vacían los buffers para asegurarnos de que la información se muestra o guarda en el momento que queremos, y también optimizar el rendimiento de nuestros programas ajustando el tamaño y tipo de buffering según nuestras necesidades.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos