Glosario¶
Introducción¶
Trabajando con archivos (de texto) en C¶
El manejo de archivos es una capacidad fundamental en la mayoría de las
aplicaciones. En C, la librería estándar de E/S (stdio.h) provee un conjunto
robusto y de bajo nivel para interactuar con el sistema de archivos. Este apunte
amplía el manejo de archivos de texto, cubriendo no solo las operaciones básicas
sino también el posicionamiento dentro del archivo y, de manera crucial, una
gestión de errores detallada y profesional.
Figure 1:Ciclo de vida físico en el manejo de archivos: Apertura, Operación y Cierre.
Desarrollo¶
El FILE, la conexión con el archivo¶
Toda operación sobre archivos en C se realiza a través de un puntero a una
estructura especial y opaca llamada FILE. Esta estructura, definida en la
biblioteca estándar <stdio.h>, actúa como un intermediario que contiene toda
la información de estado necesaria para gestionar el flujo de datos ( stream )
hacia y desde el archivo.
Figure 2:El cursor de lectura/escritura (FILE *) apuntando al stream físico en disco.
Dentro de esta estructura, el sistema operativo y la biblioteca estándar de C manejan los detalles como:
El descriptor de archivo a bajo nivel y en el sistema operativo.
La ubicación del búfer de E/S en memoria para optimizar lecturas y escrituras.
Indicadores de estado, como los flags de error y de fin de archivo (EOF).
La posición actual del cursor dentro del archivo.
Al ser una estructura opaca, vos no necesitás conocer ni manipular sus miembros internos directamente. En su lugar, interactuás con el archivo a través de funciones que reciben un puntero a esta estructura.
Para declarar un puntero a FILE, la sintaxis es simple:
FILE *puntero_archivo;Este puntero, una vez que la función fopen() lo inicializa exitosamente, se
convierte
en tu identificador único para interactuar con ese archivo específico hasta que
lo cierres con fclose().
Una Analogía con Arreglos y Punteros¶
La idea de usar un puntero para manejar una entidad compleja les debe resultar familiar, ya que hemos trabajado con arreglos. El concepto es similar y se relaciona directamente con el manejo de punteros:
En un arreglo, el nombre del arreglo es el identificador de un bloque de memoria contiguo, pero decae automáticamente a un puntero a su primer elemento (es decir, evalúa su dirección) en la mayoría de las expresiones. Esto te proporciona el punto de acceso para recorrer la secuencia contigua.
De manera análoga, un puntero
FILE *no es el archivo en sí. Es un puntero a una estructura en memoria que “sabe” todo sobre el archivo y cómo comunicarse con él.
Por ahora, es suficiente que entiendas que puntero_archivo es tu “manija” o
“handle” para leer, escribir y manipular el archivo que abriste.
Otro detalle importante: los argumentos de tipo cadena suelen declararse con la
notación char *modo (puntero a carácter). Aunque a nivel de acceso podamos
indexarlos de forma similar a un arreglo, no hay equivalencia de identidad:
una declaración como char *modo = "r" crea un puntero a un literal de cadena
almacenado en una región de memoria de solo lectura, mientras que char modo[] = "r" define un arreglo mutable en la pila que se inicializa con una copia de
dicho texto. Debemos ser conscientes de esto al manipular cadenas para evitar
accesos inválidos o intentos de escritura sobre literales.
Apertura de Archivos: fopen()¶
La función fopen() es el punto de entrada crucial para cualquier operación de
archivo. Actúa como un puente entre tu programa y el sistema de archivos del
sistema operativo. Su tarea es solicitar el acceso a un archivo específico en un
modo determinado.
Si el sistema operativo concede el acceso, fopen() reserva los recursos
necesarios, inicializa la estructura FILE con la información pertinente y te
devuelve un puntero a dicha estructura. Si por alguna razón la operación falla
(el archivo no existe, no tenés permisos, etc.), la función te devolverá NULL.
La sintaxis, definida en <stdio.h>, es la siguiente:
FILE *fopen(const char *pathname, const char *mode);Program 1:Sintaxis de fopen()
const char *pathname: Es una cadena de caracteres que representa la ruta al archivo. Puede ser una ruta relativa (ej."datos.txt") o absoluta (ej."/home/usuario/documentos/datos.txt").const char *mode: Es una cadena corta que especifica el modo de acceso, definiendo qué operaciones estarán permitidas sobre el archivo.
Modos de apertura¶
Elegir el modo correcto es fundamental, ya que determina el comportamiento del puntero del archivo y lo que sucede con el contenido que ya estaba en el archivo.
Figure 3:Modos de apertura física con fopen: lectura, escritura y agregado.
Modo | Descripción | Si el archivo no existe | Si el archivo existe | Caso de Uso Típico |
|---|---|---|---|---|
| Lectura (Read): Abre un archivo de texto para leer. | Falla (devuelve | El puntero se posiciona al inicio. | Leer un archivo de configuración, procesar datos de entrada. |
| Escritura (Write): Abre un archivo de texto para escribir. | Se crea un archivo nuevo. | El contenido se borra (trunca a cero). | Guardar un nuevo documento, generar un archivo de log desde cero. |
| Añadir (Append): Abre un archivo de texto para escribir al final. | Se crea un archivo nuevo. | El puntero se posiciona al final. Los datos existentes se conservan. | Añadir eventos a un archivo de log existente. |
| Lectura y Escritura: Abre para actualizar. | Falla (devuelve | El puntero se posiciona al inicio. Permite leer y sobreescribir. | Modificar un registro específico en un archivo de datos. |
| Escritura y Lectura: Abre para actualizar, borrando el contenido. | Se crea un archivo nuevo. | El contenido se borra. Permite escribir y luego leer desde el inicio. | Archivos temporales que necesitás escribir y luego releer. |
| Añadir y Lectura: Abre para actualizar, posicionando la escritura al final. | Se crea un archivo nuevo. | El puntero se posiciona al final para escribir, pero podés moverlo para leer. | Leer datos de un log y luego añadir nuevos eventos al final. |
Manejo de Errores en la Apertura¶
Cuando fopen() devuelve NULL, la variable global errno (definida en
<errno.h>) se establece con un código de error específico del sistema. Para
mostrar un mensaje de error legible por humanos, podés usar la función
perror().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19#include <stdio.h> #include <errno.h> // Necesario para perror() int main() { FILE *p_archivo; p_archivo = fopen("archivo_inexistente.txt", "r"); if (p_archivo == NULL) { // Imprime un mensaje descriptivo del último error ocurrido perror("Error al intentar abrir el archivo"); return 1; // Termina el programa con un código de error } printf("Archivo abierto con éxito.\n"); // ... operaciones con el archivo ... fclose(p_archivo); return 0; }
Program 2:Verificación de errores al abrir un archivo
Al ejecutar este código, perror() probablemente imprimiría algo como:
Error al intentar abrir el archivo: No such file or directory`Binario vs. Texto¶
Por defecto, los modos listados arriba operan en modo texto. Esto implica
que el sistema puede realizar conversiones automáticas de los finales de línea
para adaptarse a la convención de la plataforma (por ejemplo, convertir \n a
\r\n en Windows).
Para trabajar con archivos binarios —como imágenes, audio, ejecutables o
cualquier archivo donde cada byte importa—, es crucial evitar estas
traducciones. Para ello, simplemente agregá una b al final del modo (ej.
"rb", "wb+", "ab").
Trabajar con archivos binarios es importante, pero complejo y requiere de un par de cosas más que no hemos visto del lenguaje. Para quienes deseen chusmear como se hace, en la sección extra, hay un apunte referido a como trabajar de esta forma los archivos.
Escribiendo¶
Existen tres funciones para escribir en archivos, que van desde caracteres individuales, cadenas, y terminando en cadenas con formato.
fputc¶
La función fputc se utiliza para escribir un único carácter en un flujo de
archivo (file stream). Es una herramienta fundamental para la manipulación de
archivos a bajo nivel en C.
1 2 3 4 5 6 7 8 9 10 11 12 13 14/** * Escribe un carácter en un flujo de archivo. * * @param character a escribir. Se pasa como un `int` pero * se convierte internamente a `unsigned char`. * @param stream Puntero al objeto `FILE` que identifica el * flujo donde se escribirá el carácter. * * @return Si la operación es exitosa, devuelve el mismo * carácter que se escribió (promocionado a `int`). * Si ocurre un error, devuelve la constante `EOF` * y activa el indicador de error del flujo. */ int fputc(int character, FILE *stream);
fputs¶
Escribe una cadena. No añade el carácter de nueva línea (\n)
automáticamente. Devuelve un valor no negativo si tiene éxito, o EOF en caso
de error.
1 2 3 4 5 6 7 8 9 10/** * Escribe una cadena de caracteres en un flujo de archivo. * * @param cadena de caracteres terminada en nulo que se va a escribir. * @param stream Puntero al objeto `FILE` que identifica el flujo de salida. * * @return Devuelve un valor no negativo si la operación es exitosa. * Devuelve la constante `EOF` para indicar un error. */ int fputs(const char *cadena, FILE *stream);
fprintf¶
La opción más versátil. Escribe datos con formato, análogamente a printf().
Devuelve el número de caracteres escritos, o un valor negativo si ocurre un
error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17/** * @brief Escribe datos con formato en un flujo de archivo. * * @param stream Puntero al objeto `FILE` que identifica el * flujo de salida. * @param formato Cadena de caracteres que contiene el texto * a escribir. Puede contener especificadores * de formato (ej. %d, %f, %s) que serán reemplazados * por los argumentos subsiguientes. * @param ... Lista variable de argumentos. Debe haber un argumento * por cada especificador de formato en la cadena `format`. * * @return Si la operación es exitosa, devuelve el número total de caracteres escritos. * Si ocurre un error de escritura, devuelve un número negativo. */ int fprintf(FILE *stream, const char *formato, ...);
Ejemplo de escritura completo¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(void) { // 1. Abrir el archivo en modo escritura ("w"). FILE *salida = fopen("factura_completa.txt", "w"); if (salida == NULL) { perror("Error al abrir el archivo 'factura_completa.txt'"); return EXIT_FAILURE; } // 2. Escribir un encabezado usando fputs() // fputs() escribe una cadena de caracteres en el archivo. const char *encabezado = "--- Documento de Factura ---\n\n"; if (fputs(encabezado, salida) == EOF) { perror("Error escribiendo el encabezado con fputs()"); fclose(salida); return EXIT_FAILURE; } // 3. Escribir datos formateados usando fprintf() // fprintf() permite escribir datos con formato (como printf, pero a un archivo). const char *item_1 = "Placa de Video RTX 4080"; int cantidad_1 = 1; double precio_1 = 1200000.75; int chars_escritos_1 = fprintf(salida, "Item: %s\nCantidad: %d\nPrecio: %.2f ARS\n\n", item_1, cantidad_1, precio_1); if (chars_escritos_1 < 0) { perror("Error al formatear y escribir el item 1 con fprintf()"); fclose(salida); return EXIT_FAILURE; } // 4. Escribir un separador de línea usando fputc() // fputc() escribe un solo carácter en el archivo. int i; for (i = 0; i < 30; i++) { if (fputc('-', salida) == EOF) { perror("Error escribiendo separador con fputc()"); fclose(salida); return EXIT_FAILURE; } } if (fputc('\n', salida) == EOF) { perror("Error escribiendo nueva linea con fputc()"); fclose(salida); return EXIT_FAILURE; } // 5. Escribir otro item usando una combinación de las tres funciones. const char *item_2 = "Memoria RAM DDR5 32GB"; int cantidad_2 = 2; double precio_2 = 180000.00; if (fputs("Detalle del Item 2:\n", salida) == EOF) { perror("Error escribiendo detalle del item 2 con fputs()"); fclose(salida); return EXIT_FAILURE; } if (fprintf(salida, " Nombre: %s\n", item_2) < 0) { perror("Error escribiendo nombre del item 2 con fprintf()"); fclose(salida); return EXIT_FAILURE; } if (fprintf(salida, " Unidades: %d\n", cantidad_2) < 0) { perror("Error escribiendo unidades del item 2 con fprintf()"); fclose(salida); return EXIT_FAILURE; } if (fprintf(salida, " Valor Unitario: %.2f ARS\n", precio_2) < 0) { perror("Error escribiendo valor unitario del item 2 con fprintf()"); fclose(salida); return EXIT_FAILURE; } // 6. Cerrar el archivo. Es crucial para asegurar que todos los datos en el búfer se guarden en el disco. if (fclose(salida) != 0) { perror("Error al cerrar el archivo"); return EXIT_FAILURE; } printf("Archivo 'factura_completa.txt' creado y cerrado exitosamente.\n"); return EXIT_SUCCESS; }
Program 3:Ejemplo de escritura y gestión de errores
Leyendo¶
fgetc¶
La función fgetc se utiliza para leer un único carácter desde un flujo de
archivo. Es la contraparte directa de fputc.
1 2 3 4 5 6 7 8 9 10 11/** * @brief Lee un carácter desde un flujo de archivo. * * @param stream Puntero al objeto `FILE` que identifica el flujo de entrada. * * @return Si la operación es exitosa, devuelve el carácter leído (promocionado a `int`). * @return Si se alcanza el final del archivo o si ocurre un error, devuelve `EOF`. */ int fgetc(FILE *stream);
fgets¶
La función fgets se utiliza para leer una línea o una cadena de caracteres
desde un flujo de archivo. Es más segura que la antigua función gets porque
permite especificar un tamaño máximo para el búfer, evitando desbordamientos,
una práctica recomendada por la regla Regla 0x5006h: Preferí fgets sobre gets y scanf para leer cadenas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18/** * @brief Lee una cadena de caracteres desde un flujo de archivo. * * La lectura se detiene cuando se encuentra un carácter de nueva línea (`\n`), * cuando se alcanza el final del archivo (EOF), o después de que se hayan * leído (num - 1) caracteres. El carácter de nueva línea, si es leído, * se incluye en la cadena. Se añade un carácter nulo (`\0`) al final. * * @param cadena de caracteres donde se almacenará la cadena leída. * @param numero máximo de caracteres a ser leídos (incluyendo el carácter nulo final). * @param stream Puntero al objeto `FILE` que identifica el flujo de entrada. * * @return En caso de éxito, devuelve el puntero `str`. * Si se alcanza el final del archivo antes de leer algún carácter, o si ocurre un error, devuelve `NULL`. */ char *fgets(char *cadena, int numero, FILE *stream);
fscanf¶
La función fscanf se utiliza para leer datos con formato desde un flujo de
archivo. Funciona de manera análoga a scanf, pero operando sobre un archivo en
lugar de la entrada estándar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15/** * @brief Lee datos con formato desde un flujo de archivo. * * @param[in] stream Puntero al objeto `FILE` que identifica el flujo de entrada. * @param[in] format Cadena de caracteres que especifica cómo interpretar los datos leídos. * @param[out] ... Lista variable de punteros a las variables donde se almacenarán los datos leídos. * * @return Devuelve el número de elementos de entrada asignados exitosamente. * @return Puede devolver `EOF` si se encuentra el final del archivo o ocurre un error antes de la primera asignación. */ int fscanf(FILE *stream, const char *format, ...);
Leyendo un archivo, paso a paso¶
El código de ejemplo es una demostración de cómo leer un archivo de texto de
manera segura y eficiente en C. La estrategia principal es separar la
entrada/salida (E/S) del procesamiento de datos. En lugar de intentar
interpretar los datos directamente desde el archivo con fscanf(), lee el
archivo línea por línea en un espacio de memoria temporal (un búfer) y luego
analiza esa línea. Este enfoque es más resiliente a errores de formato.
A continuación, se descompone el código sección por sección.
1. Inclusiones y definiciones (#include y #define)¶
1 2 3 4 5 6#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_LINEA 512 #define NOMBRE_ARCHIVO "factura.txt"
#include <stdio.h>: Incluye la biblioteca estándar de entrada/salida. Es fundamental porque nos da acceso a funciones comofopen(),fgets(),printf(),perror(),fclose(),ferror()yfeof().#include <stdlib.h>: Proporciona acceso a utilidades generales, incluyendo las macrosEXIT_SUCCESSyEXIT_FAILURE, que son una forma estándar y portable de indicar si el programa terminó con o sin errores.#include <string.h>: Incluye la biblioteca para manipulación de cadenas de caracteres. La usamos aquí para la funciónstrncmp().#define MAX_LINEA 512: Define una constante para el tamaño máximo de línea que esperamos leer. Usar una constante hace que el código sea más legible y fácil de mantener. Si necesitáramos cambiar el tamaño del búfer, solo lo hacemos acá. El valor de 512 bytes es una elección común y razonable para muchas aplicaciones.#define NOMBRE_ARCHIVO "factura.txt": Define el nombre del archivo que vamos a leer. Al igual que conMAX_LINEA, esto centraliza la configuración y mejora la mantenibilidad del código.
2. Apertura del archivo y manejo de errores¶
1 2 3 4 5FILE *entrada = fopen(NOMBRE_ARCHIVO, "r"); if (!entrada) { perror("No se pudo abrir 'factura.txt' para lectura"); return EXIT_FAILURE; }
FILE *entrada = fopen(NOMBRE_ARCHIVO, "r");: Esta línea intenta abrir el archivo especificado porNOMBRE_ARCHIVOen modo lectura ("r"). Si tiene éxito,fopen()devuelve un puntero a una estructuraFILE, que es un handle o manejador que representa el flujo de datos (el stream). Si falla (por ejemplo, porque el archivo no existe o no tenemos permisos de lectura), devuelveNULL.if (!entrada) { ... }: Esta es la comprobación de errores crítica. Nunca debés asumir quefopen()funcionó. SientradaesNULL(lo que!entradaevalúa como verdadero), el programa no puede continuar.perror(...): Es una función muy útil para reportar errores. Imprime en la salida de error estándar (stderr) el mensaje que le pasás como argumento, seguido de dos puntos y una descripción textual del último error ocurrido en el sistema. Por ejemplo, si el archivo no existe, podría imprimir:No se pudo abrir 'factura.txt' para lectura: No such file or directory.return EXIT_FAILURE;: Termina el programa indicando al sistema operativo que ocurrió un error.
3. Lazo principal de lectura¶
1 2 3 4 5 6 7char buffer[MAX_LINEA]; int numero_linea = 0; while (fgets(buffer, sizeof(buffer), entrada) != NULL) { numero_linea++; // ... procesamiento de la línea ... }
char buffer[MAX_LINEA];: Declara el búfer, un array de caracteres dondefgets()almacenará cada línea leída del archivo.while (fgets(...) != NULL): Este es el corazón del programa.fgets(buffer, sizeof(buffer), entrada): Intenta leer una línea del flujoentrada.buffer: El destino donde se guardará la línea.sizeof(buffer): El tamaño máximo a leer (en este caso, 512).fgets()es segura porque nunca escribirá más allá de este límite, previniendo desbordamientos de búfer. Leerá hastasizeof(buffer) - 1caracteres y agregará un\0al final.entrada: El flujo de archivo del cual leer.
La función
fgets()devuelveNULLcuando llega al final del archivo o si ocurre un error de lectura. Mientras no devuelvaNULL, el lazowhilecontinúa ejecutándose, procesando una línea en cada iteración.
4. Procesamiento y análisis de cada línea (Parsing)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15if (strncmp(buffer, "Item:", 5) == 0) { char item_nombre[100]; int cantidad; double precio; int campos_leidos = sscanf(buffer, "Item: %99[^,], Cantidad: %d, Precio: %lf ARS", item_nombre, &cantidad, &precio); if (campos_leidos == 3) { // ... éxito ... } else { // ... fallo ... } }
if (strncmp(buffer, "Item:", 5) == 0): Antes de intentar un análisis costoso, se realiza una verificación rápida y eficiente.strncmp()compara los primeros5caracteres delbuffercon la cadena"Item:". Si son iguales, la función devuelve0, y procedemos a analizar la línea. Esto nos permite ignorar líneas en blanco o comentarios de forma eficiente.sscanf(buffer, "...", ...): Esta es la función clave para el parsing. A diferencia defscanf(), que lee desde un archivo,sscanf()lee desde una cadena de caracteres que ya está en memoria (elbuffer).La cadena de formato: Es la plantilla que
sscanf()usa para interpretar los datos."Item: "y", Cantidad: "y", Precio: "y" ARS": Son caracteres literales. La cadena en el búfer debe coincidir exactamente con estos literales (incluyendo los espacios).%99[^,]: Este es un especificador de formato avanzado y muy seguro.%[...]: Indica que se debe leer un conjunto de caracteres (un scanset).^,: El^niega el conjunto. Esto significa “leer todos los caracteres excepto la coma”.99: Es un limitador de ancho. Le dice asscanf()que lea como máximo 99 caracteres para este campo. Esto previene un desbordamiento del búferitem_nombre(que tiene tamaño 100, dejando espacio para el\0).
%d: Lee un número entero decimal.%lf: Lee un número de punto flotante de tipodouble(lales delong float, que significadouble).
int campos_leidos = sscanf(...):sscanf()devuelve el número de campos que pudo asignar con éxito. Guardamos este valor.if (campos_leidos == 3): Comprobamos sisscanf()pudo leer y asignar los 3 campos que esperábamos (item_nombre,cantidadyprecio). Si es así, el parsing fue exitoso. Si no, la línea no tenía el formato correcto y lo informamos como una advertencia.
5. Verificación post-lazo¶
1 2 3 4 5if (ferror(entrada)) { perror("Ocurrió un error de lectura en el archivo"); } else if (feof(entrada)) { printf("\nProcesamiento completado. Se llegó al final del archivo.\n"); }
Cuando el lazo
while (fgets(...) != NULL)termina, hay dos posibles razones: se alcanzó el final del archivo (lo normal) o ocurrió un error de E/S (raro, pero posible). Es crucial distinguir entre ambos casos.ferror(entrada): Esta función devuelve un valor verdadero si el indicador de error del streamentradaestá activado. Esto podría pasar si, por ejemplo, el disco duro falla o se desconecta una unidad USB a mitad de la lectura.feof(entrada): Devuelve un valor verdadero si el indicador de fin de archivo (End-Of-File) del stream está activado. Esta es la condición de salida normal y esperada del lazo.
6. Limpieza y cierre¶
1 2 3clearerr(entrada); fclose(entrada); return EXIT_SUCCESS;
clearerr(entrada): Limpia los indicadores de error y de fin de archivo del stream. Es una buena práctica, aunque en este caso el programa está a punto de terminar y no es estrictamente necesario.fclose(entrada): Cierra el archivo. Esto es fundamental. Libera los recursos que el sistema operativo había asignado para manejar el archivo, como el puntero aFILEy los búferes internos. No cerrar un archivo puede llevar a la pérdida de datos (especialmente en escritura) y al agotamiento de recursos del sistema.return EXIT_SUCCESS;: Informa al sistema operativo que el programa terminó exitosamente.
Cierre de Archivos: fclose(), el Paso Final¶
fclose(FILE *stream) disocia el archivo del puntero FILE. Es una operación
crítica que:
Vacía el búfer de salida: Asegura que todos los datos escritos con funciones como
fprintfofputsse escriban físicamente en el disco.Libera recursos del sistema: El sistema operativo tiene un límite en la cantidad de archivos que un proceso puede tener abiertos simultáneamente.
Devuelve 0 si tiene éxito y EOF si ocurre un error.
Aunque parezca una simple formalidad, la llamada a fclose() también puede
fallar. Esto es particularmente cierto al escribir archivos: si el disco se
llena o el medio de almacenamiento se desconecta, el vaciado final del búfer (el
flush) fallará. Ignorar el valor de retorno de fclose() podría hacerte creer
que la operación fue exitosa cuando en realidad los últimos datos se perdieron.
La única forma de estar 100% seguro de que toda la información se guardó
correctamente es verificar el resultado del cierre.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28#include <stdio.h> #include <stdlib.h> int main(void) { FILE *log_file = fopen("app.log", "a"); if (log_file == NULL) { perror("No se pudo abrir el log"); return EXIT_FAILURE; } fprintf(log_file, "El programa inició una operación crítica.\n"); // ... el resto del programa ... fprintf(log_file, "La operación crítica finalizó.\n"); // Cerramos el archivo y VERIFICAMOS el resultado. if (fclose(log_file) != 0) { // Si fclose falla, el error queda registrado en errno. perror("FALLO CRÍTICO al cerrar el archivo de log"); // En un programa real, esto podría requerir una acción de emergencia, // ya que los últimos datos podrían no haberse guardado. return EXIT_FAILURE; } printf("Log escrito y cerrado correctamente.\n"); return EXIT_SUCCESS; }
Program 4:Verificación del cierre de un archivo
Funciones y variables para la gestión de errores¶
stderr: El flujo de error estándar¶
En C, tenés tres flujos de comunicación estándar:
stdin(entrada estándar): Generalmente el teclado.stdout(salida estándar): Generalmente la pantalla.stderr(error estándar): También, generalmente la pantalla.
¿Por qué separar stdout de stderr? Para poder redirigir la salida.
Imaginate que ejecutás tu programa y guardás el resultado en un archivo:
./mi_programa > salida.txt
Si usaras printf (que escribe en stdout) para los errores, estos quedarían
mezclados con los datos correctos en salida.txt, haciendo difícil su
detección. Al usar fprintf(stderr, ...) o perror(), los mensajes de error se
imprimen en la consola por separado, permitiéndote ver los problemas incluso
cuando la salida “buena” está siendo redirigida.
Uso general: Se utilizá stderr para mensajes de error, diagnósticos o
advertencias.
errno: El código del último error¶
errno es una variable global (técnicamente, una macro que se expande a una
expresión modificable) definida en <errno.h>. Las funciones de sistema y de la
biblioteca estándar de C la utilizan para comunicar qué salió mal cuando
fallan.
No lo reinicies: Nunca asignes
errno = 0;manualmente. Su valor solo es válido inmediatamente después de que una función falle.No lo verifiques si la función tuvo éxito: Si
fopen()no devuelveNULL, el valor deerrnoes irrelevante y podría contener un valor “viejo” de un error anterior.
Uso general: Consultá errno solo después de haber detectado que una
función ha fallado (por ejemplo, verificando un retorno NULL o -1).
perror(const char *s): El informador directo¶
perror es la forma más sencilla de reportar un error. Hace dos cosas:
Imprime la cadena que le pasaste como argumento.
Inmediatamente después, imprime dos puntos (
:) y la descripción textual correspondiente al valor actual deerrno.
Situación de uso: Ideal para herramientas de línea de comandos o scripts donde necesitás un mensaje de error rápido, estándar y sin formato complejo. Es menos flexible pero muy conveniente.
// Si errno es 2 ("No such file or directory")
perror("Error al leer el archivo de configuración");
// Salida en stderr:
// Error al leer el archivo de configuración: No such file or directorystrerror(int errnum): El traductor flexible¶
strerror te da más control. Toma un número de error (casi siempre le pasarás
errno) y devuelve un puntero a una cadena de caracteres (char *) con la
descripción del error. Vos sos responsable de cómo y dónde imprimir esa cadena.
Situación de uso: Imprescindible cuando necesitás:
Formatear el mensaje de error de una manera específica.
Enviar el mensaje de error a un destino diferente a
stderr, como un archivo de log, un cuadro de diálogo en una GUI, o a través de la red.Internacionalización, donde podrías querer traducir el mensaje de error a diferentes idiomas.
// Si errno es 13 ("Permission denied")
fprintf(stderr, "[FATAL] Imposible acceder al recurso. Razón: %s\n",
strerror(errno));
// Salida en stderr:
// [FATAL] Imposible acceder al recurso. Razón: Permission deniedPosicionamiento en Archivos: Acceso Aleatorio¶
No siempre querés leer un archivo secuencialmente. Las funciones de posicionamiento te permiten moverte a cualquier punto del archivo.
ftell¶
La función ftell se utiliza para obtener la posición actual del indicador de
posición del fichero (el “cursor”) dentro de un flujo. Devuelve esta posición
como un número de bytes desde el inicio del archivo.
1 2 3 4 5 6 7 8 9 10/** * @brief Obtiene la posición actual del indicador de posición del fichero. * * @param[in] stream Puntero al objeto `FILE` que identifica el flujo. * * @return Si es exitoso, devuelve el valor actual del indicador de posición. * @return En caso de error, devuelve -1L y la variable global `errno` se establece a un valor positivo. */ long int ftell(FILE *stream);
fseek¶
La función fseek es la herramienta principal para mover el indicador de
posición del fichero a una ubicación específica dentro del flujo. Permite un
control preciso, moviendo el cursor un número determinado de bytes (offset)
desde un punto de origen (origin).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15/** * @brief Establece el indicador de posición del fichero a una nueva posición. * * @param stream Puntero al objeto `FILE` que identifica el flujo. * @param offset Desplazamiento en bytes relativo al parámetro `origin`. * @param origin Posición desde donde se calcula el desplazamiento. Los valores pueden ser: * - `SEEK_SET`: Inicio del archivo. * - `SEEK_CUR`: Posición actual. * - `SEEK_END`: Final del archivo. * * @return Devuelve 0 si la operación es exitosa. * Devuelve un valor distinto de cero en caso de error. */ int fseek(FILE *stream, long int offset, int origin);
rewind¶
La función rewind es un caso especial y simplificado de fseek. Su única
función es mover el indicador de posición del fichero de vuelta al inicio del
archivo. Además, limpia cualquier indicador de error que pudiera tener el flujo.
1 2 3 4 5 6 7 8 9/** * Reposiciona el indicador de posición del fichero al inicio del flujo. * * Esta función es funcionalmente equivalente a fseek(stream, 0L, SEEK_SET), * pero además borra el indicador de error del flujo. * * @param stream Puntero al objeto `FILE` que identifica el flujo. */ void rewind(FILE *stream);
Ejemplo de uso¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47#include <stdio.h> #include <stdlib.h> int main(void) { FILE *archivo = fopen("factura.txt", "r"); if (!archivo) { perror("No se pudo abrir el archivo"); return EXIT_FAILURE; } // Moverse al final del archivo if (fseek(archivo, 0, SEEK_END) != 0) { perror("Error en fseek a SEEK_END"); fclose(archivo); return EXIT_FAILURE; } // Obtener la posición actual, que es el tamaño del archivo long tamano = ftell(archivo); if (tamano == -1L) { perror("Error en ftell"); fclose(archivo); return EXIT_FAILURE; } printf("El archivo tiene %ld bytes.\n", tamano); // Moverse a la posición ANTERIOR al último byte para leerlo. // Si el archivo termina con \n, esto leerá el carácter previo. if (tamano > 1 && fseek(archivo, -2L, SEEK_END) != 0) { perror("Error en fseek para leer el último carácter"); fclose(archivo); return EXIT_FAILURE; } int ultimo_caracter = fgetc(archivo); if (ultimo_caracter != EOF) { printf("El último carácter imprimible del archivo es: \x27%c\x27\n", (char)ultimo_caracter); } // Volver al principio rewind(archivo); printf("Después de \x27rewind\x27, la posición es: %ld\n", ftell(archivo)); fclose(archivo); return EXIT_SUCCESS; }
Program 5:Uso de fseek() y ftell() para leer el último carácter
Glosario¶
- Búfer
En el contexto de la programación y los sistemas operativos, un búfer (del inglés buffer) es una región de memoria física (generalmente en la
RAM) que se utiliza para almacenar datos de forma temporal mientras se transfieren de un lugar a otro.El objetivo principal de un búfer es optimizar el rendimiento y gestionar las diferencias de velocidad entre dos procesos o dispositivos. Por ejemplo, en las operaciones de entrada/salida (
I/O), los datos se acumulan en un búfer antes de ser procesados o escritos en un dispositivo físico como un disco duro. Esto permite que el sistema realice menos operaciones de escritura/lectura, pero de mayor tamaño, lo cual es significativamente más eficiente.Pensá en el proceso de escribir en un archivo como si fuera enviar una carta. Escribir carácter por carácter directamente al disco (sin búfer) sería como llevar cada letra individualmente hasta el correo. Es ineficiente y lento.
Usar un búfer es como escribir la carta completa en una hoja de papel (el búfer en la memoria). Una vez que terminaste la carta (el búfer se llenó o cerraste el archivo), la llevás al correo en un solo viaje. Este método es mucho más rápido y organizado.
Figure 4:Comparación entre operaciones sin búfer (ineficientes) y con búfer (eficientes), mostrando cómo el búfer optimiza las operaciones de E/S.
Ejercicios de Autoevaluación¶
Solution to Exercise 1
La estructura FILE es opaca porque sus detalles de implementación dependen de
la biblioteca estándar del compilador y del sistema operativo subyacente. Si
accedés directamente a sus miembros (como el descriptor de archivo, la posición
del búfer o los flags de error):
Perdés la portabilidad: un código que compila en un sistema podría fallar en otro donde la estructura
FILEtenga campos diferentes.Podés corromper el estado interno del flujo de datos (stream) al eludir las funciones estandarizadas de E/S (
fopen,fread,fwrite, etc.) que controlan la sincronización del búfer y el cursor.
Al tratarla como opaca, el estándar garantiza una interfaz uniforme e
independiente de la plataforma a través de punteros (FILE *).
Solution to Exercise 2
El error radica en que se declaró archivo como una variable de tipo FILE en
lugar de un puntero a FILE (FILE *).
La función
fopendevuelve un puntero (FILE *), no la estructura en sí.Como
FILEes una estructura opaca, el compilador usualmente no conoce su tamaño completo o no permite instanciarla directamente en la pila, lo que provoca un error de compilación. La declaración correcta debe serFILE *archivo;.
Solution to Exercise 3
Para procesar tres archivos en forma simultánea, debés declarar tres punteros
FILE * independientes (por ejemplo, FILE *f1, *f2, *f3;) e invocar fopen
para cada uno de ellos.
Si intentás reutilizar una única variable de tipo puntero (por ejemplo, f1 = fopen("a.txt", "r"); f1 = fopen("b.txt", "r");) sin cerrar la primera con
fclose:
Pérdida del handle: Perdés la dirección de memoria de la estructura
FILEdel primer archivo. Ya no podrás leer de él ni cerrarlo correctamente, lo que provoca una fuga de recursos (resource leak) en el sistema operativo.Comportamiento indefinido: El descriptor del primer archivo queda abierto en el sistema hasta que finalice el programa.
Solution to Exercise 4
El siguiente código abre el archivo y realiza la verificación obligatoria del
puntero devuelto ({ref}0x4001h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15#include <stdio.h> #include <stdlib.h> int main(void) { FILE *archivo = fopen("config.json", "r"); if (archivo == NULL) { perror("Error al intentar abrir 'config.json'"); return EXIT_FAILURE; } // Aquí irían las operaciones con el archivo... fclose(archivo); return EXIT_SUCCESS; }
Solution to Exercise 5
El modo de apertura adecuado es "a" (añadir/append) o "a+" (si además
necesitás realizar operaciones de lectura).
Justificación:
Si el archivo existe: El cursor se posiciona automáticamente al final del archivo y cualquier escritura se agregará allí, protegiendo y conservando la información que ya estaba en el archivo.
Si el archivo no existe: El sistema operativo crea el archivo de forma automática. Si hubieras utilizado
"w", el archivo existente se habría truncado a cero bytes, perdiendo toda la información previa.
Solution to Exercise 6
La función implementa una estrategia de validación previa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14#include <stdio.h> FILE *abrir_para_escribir_seguro(const char *nombre_archivo) { FILE *archivo_existente = fopen(nombre_archivo, "r"); if (archivo_existente != NULL) { // El archivo ya existe, lo cerramos y retornamos NULL fclose(archivo_existente); return NULL; } // Si no existe, fopen("...", "r") devolvió NULL. // Ahora es seguro abrirlo con "w". return fopen(nombre_archivo, "w"); }
Solution to Exercise 7
El programa utiliza un lazo para recorrer los caracteres ASCII y los escribe uno a uno:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33#include <stdio.h> #include <stdlib.h> int main(void) { FILE *archivo = fopen("alfabeto.txt", "w"); if (archivo == NULL) { perror("Error al crear el archivo"); return EXIT_FAILURE; } // Se recorren los caracteres ASCII de la 'A' a la 'Z' usando un lazo for (char c = 'A'; c <= 'Z'; c++) { if (fputc(c, archivo) == EOF) { perror("Error al escribir el caracter"); fclose(archivo); return EXIT_FAILURE; } if (c != 'Z') { if (fputc(' ', archivo) == EOF) { perror("Error al escribir el espacio"); fclose(archivo); return EXIT_FAILURE; } } } if (fclose(archivo) != 0) { perror("Error al cerrar el archivo"); return EXIT_FAILURE; } return EXIT_SUCCESS; }
Solution to Exercise 8
La función utiliza fprintf y valida los valores de retorno para asegurar que
la escritura fue correcta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21#include <stdio.h> int guardar_matriz(FILE *salida, int matriz[3][3], int filas, int columnas) { if (salida == NULL) { return -1; } for (int i = 0; i < filas; i++) { for (int j = 0; j < columnas; j++) { // Escribimos cada elemento formateado con un tabulador if (fprintf(salida, "%d\t", matriz[i][j]) < 0) { return -1; // Error en fprintf } } // Agregamos un salto de línea al final de cada fila if (fputc('\n', salida) == EOF) { return -1; // Error en fputc } } return 0; // Éxito }
Solution to Exercise 9
fputs es una función diseñada exclusivamente para escribir cadenas de
caracteres literales en un archivo, sin realizar ninguna interpretación ni
procesamiento de formato.
Por otro lado, fprintf debe analizar la cadena de formato (buscando los %),
evaluar los tipos de datos de los argumentos variables pasados al final y
convertirlos a texto en tiempo de ejecución.
Por ende:
Rendimiento:
fputses significativamente más rápida porque no tiene el costo computacional de análisis y conversión de formato.Simplicidad: Para cadenas fijas (ej. un encabezado como
"--- Fin de reporte ---\n"),fputses más directa y segura. Debés elegirfprintfsolo cuando necesites concatenar o formatear variables (como enteros, reales, etc.) en una cadena textual en el archivo.
Solution to Exercise 10
La función abre el archivo en modo de lectura y utiliza un lazo while para
leer y mostrar cada carácter hasta llegar a EOF:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21#include <stdio.h> #include <stdlib.h> void mostrar_archivo(const char *nombre_archivo) { FILE *archivo = fopen(nombre_archivo, "r"); if (archivo == NULL) { perror("No se pudo abrir el archivo para mostrar su contenido"); return; } int caracter; // Debe ser int para almacenar EOF correctamente while ((caracter = fgetc(archivo)) != EOF) { putchar(caracter); // Imprime en la salida estándar } if (ferror(archivo)) { perror("Ocurrió un error de lectura en el archivo"); } fclose(archivo); }
Solution to Exercise 11
Cuando una línea excede el tamaño del búfer de fgets, la función lee la línea
en “fragmentos”. Solo debemos contar una nueva línea cuando encontremos el
carácter de salto de línea \n al final del búfer, o cuando la lectura termine
y hayamos leído caracteres en la última línea:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29#include <stdio.h> #include <string.h> int contar_lineas(FILE *archivo) { if (archivo == NULL) { return -1; } char buffer[256]; int lineas = 0; int en_linea = 0; // Flag para rastrear si estamos dentro de una línea while (fgets(buffer, sizeof(buffer), archivo) != NULL) { en_linea = 1; size_t len = strlen(buffer); // Si el último carácter es un salto de línea, se completa una línea entera if (len > 0 && buffer[len - 1] == '\n') { lineas++; en_linea = 0; } } // Si terminó el archivo pero la última línea no terminaba con '\n' if (en_linea) { lineas++; } return lineas; }
Solution to Exercise 12
El código implementa la recomendación de separar la lectura del parsing,
utilizando un búfer de línea y sscanf con validación de campos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32#include <stdio.h> #include <stdlib.h> #define MAX_L 256 int main(void) { FILE *archivo = fopen("estudiantes.txt", "r"); if (archivo == NULL) { perror("Error al abrir 'estudiantes.txt'"); return EXIT_FAILURE; } char buffer[MAX_L]; while (fgets(buffer, sizeof(buffer), archivo) != NULL) { char nombre[100]; int nota1, nota2; // sscanf lee desde el buffer en memoria. // %99[^,] lee hasta 99 caracteres que no sean comas. int campos = sscanf(buffer, "%99[^,],%d,%d", nombre, ¬a1, ¬a2); if (campos == 3) { double promedio = (nota1 + nota2) / 2.0; printf("Estudiante: %s | Promedio: %.2f\n", nombre, promedio); } else { fprintf(stderr, "Advertencia: línea con formato incorrecto.\n"); } } fclose(archivo); return EXIT_SUCCESS; }
Solution to Exercise 13
Una fuga de descriptores de archivo ocurre cuando un programa abre un archivo
usando fopen (u otras llamadas del sistema) pero nunca lo cierra mediante
fclose antes de perder la referencia al puntero FILE *.
Consecuencias:
El sistema operativo asigna un identificador único (un número entero de bajo nivel) a cada archivo abierto y tiene un límite estricto de cuántos descriptores puede abrir un único proceso simultáneamente.
Si un servidor abre archivos de configuración o logs en cada conexión entrante de clientes y olvida cerrarlos, irá agotando este límite de forma gradual.
Una vez alcanzado el límite máximo, cualquier intento posterior de abrir archivos (o incluso sockets de red) fallará con un error, haciendo que el servidor no pueda responder o colapse por completo.
Solution to Exercise 14
La verificación en el cierre es indispensable porque fclose fuerza la
escritura física de los datos acumulados en los búferes de memoria del sistema
operativo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24#include <stdio.h> #include <stdlib.h> int main(void) { FILE *archivo = fopen("critico.txt", "w"); if (archivo == NULL) { perror("Error al abrir"); return EXIT_FAILURE; } if (fprintf(archivo, "Transaccion ID: %d, Monto: %.2f\n", 1024, 45000.50) < 0) { perror("Error en escritura"); fclose(archivo); return EXIT_FAILURE; } // Verificamos el cierre del archivo if (fclose(archivo) != 0) { perror("Error al cerrar el archivo. Los datos pudieron haberse perdido"); return EXIT_FAILURE; } return EXIT_SUCCESS; }
Justificación: Para evitar operaciones lentas, el sistema operativo almacena
temporalmente los datos en la memoria RAM (el búfer). Cuando llamás a fclose,
estos datos se transfieren al disco rígido. Si el disco se llenó o el medio se
desconectó en ese instante, la escritura final fallará y la única forma de
enterarte es evaluando el retorno de fclose.
Solution to Exercise 15
El fragmento de código presenta dos problemas de seguridad graves:
Doble cierre (Double Close): Se llama a
fclosesobre el mismo punteroarchivodos veces consecutivas (una dentro deliberar_recursosy otra en elmain). Esto produce un comportamiento indefinido. En sistemas reales, puede causar accesos inválidos de memoria o cerrar accidentalmente otro archivo que haya sido abierto por el sistema en el mismo hilo y al que se le haya asignado el mismo descriptor.Puntero huérfano: Tras el primer
fclose, la variablearchivosigue apuntando a la dirección de memoria de la estructuraFILEque ya fue liberada. Para solucionarlo, se debe evitar el doble llamado o asignararchivo = NULL;inmediatamente después de un cierre exitoso, permitiendo realizar una verificación previa.
Solution to Exercise 16
La función implementa la separación de flujos estándar:
1 2 3 4 5 6 7 8 9 10 11#include <stdio.h> void registrar_log(int gravedad, const char *mensaje) { if (gravedad == 0) { // Mensaje ordinario de información fprintf(stdout, "[INFO] %s\n", mensaje); } else if (gravedad == 1) { // Mensaje de error crítico fprintf(stderr, "[ERROR] %s\n", mensaje); } }
Explicación:
En los sistemas operativos modernos, podés redirigir la salida estándar del
programa a un archivo usando ./programa > salida.txt. Al usar stderr para
los errores, estos no se mezclan en salida.txt y se siguen visualizando
directamente en la terminal. También podés redirigir únicamente los errores a un
archivo de log específico mediante ./programa 2> errores.log.
Solution to Exercise 17
El código requiere incluir los encabezados <errno.h> y <string.h> para
realizar el formateo del error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(void) { FILE *conf = fopen("/carpeta_inexistente/config.json", "r"); if (conf == NULL) { // Obtenemos la causa textual del error a partir del código numérico en errno const char *causa = strerror(errno); fprintf(stderr, "[LOG_ERROR] Fallo al iniciar el sistema. Causa: %s (Codigo: %d)\n", causa, errno); return EXIT_FAILURE; } fclose(conf); return EXIT_SUCCESS; }
Solution to Exercise 18
perror es una función simple y rígida: siempre imprime el mensaje provisto,
seguido de : y el error del sistema en el flujo stderr.
strerror es una función de conversión: recibe el código de error y devuelve
una cadena de caracteres (char *). Esto la hace mucho más flexible por tres
motivos:
Destino alternativo: Si querés registrar el error en un archivo físico en lugar de mostrarlo por consola (por ejemplo, escribiéndolo en un archivo de log con
fprintf(archivo_log, "%s", strerror(errno))),perrorno te sirve porque escribe directamente enstderr.Formateo: Si necesitás incrustar el mensaje en medio de una frase compleja (ej:
"[Transaccion fallida] - Codigo: %d - Detalle: %s\n"),strerrores obligatoria.Internacionalización: Si tu software debe mostrar los errores adaptados a diferentes lenguajes o a una interfaz gráfica de usuario (GUI), debés usar
strerrorpara tomar el texto y pasárselo a la ventana de tu aplicación.
Solution to Exercise 19
El proceso requiere posicionar el cursor al final del archivo para consultar la distancia en bytes al origen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28#include <stdio.h> #include <stdlib.h> long obtener_tamano(const char *nombre_archivo) { FILE *archivo = fopen(nombre_archivo, "r"); if (archivo == NULL) { perror("No se pudo abrir el archivo"); return -1; } // Desplazamos el cursor 0 bytes relativos al final del archivo (SEEK_END) if (fseek(archivo, 0L, SEEK_END) != 0) { perror("Error de posicionamiento en fseek"); fclose(archivo); return -1; } // ftell nos da la posición actual del cursor (que equivale al tamaño del archivo en bytes) long tamano = ftell(archivo); if (tamano == -1L) { perror("Error al obtener la posicion con ftell"); fclose(archivo); return -1; } fclose(archivo); return tamano; }
Solution to Exercise 20
El programa debe utilizar el modo "r+" que permite lectura y escritura
simultáneas sin truncar el archivo a cero bytes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32#include <stdio.h> #include <stdlib.h> int main(void) { // Abrimos el archivo en modo actualizacion (r+) FILE *archivo = fopen("datos.txt", "r+"); if (archivo == NULL) { perror("Error al abrir el archivo en modo r+"); return EXIT_FAILURE; } // Movemos el cursor al quinto byte (desplazamiento de 4 desde el inicio) if (fseek(archivo, 4L, SEEK_SET) != 0) { perror("El archivo no tiene suficientes bytes para posicionarse"); fclose(archivo); return EXIT_FAILURE; } // Sobreescribimos el carácter if (fputc('X', archivo) == EOF) { perror("Error al intentar sobreescribir el caracter"); fclose(archivo); return EXIT_FAILURE; } if (fclose(archivo) != 0) { perror("Error al cerrar el archivo"); return EXIT_FAILURE; } return EXIT_SUCCESS; }
Solution to Exercise 21
Aunque ambas operaciones mueven el indicador de posición (el cursor) de vuelta al principio del archivo (byte 0), existen diferencias fundamentales:
Indicador de error:
rewindborra automáticamente el indicador de error del flujo (como si invocarasclearerr), mientras quefseekno lo altera. Si el archivo tenía activo un flag de error de lectura previo,fseekmantendrá ese flag encendido, impidiendo operaciones futuras, mientras querewindlo restablecerá a un estado limpio.Valor de retorno:
fseekdevuelve un entero (0si tiene éxito o un valor distinto en caso de error), lo que te obliga a validar la llamada. En cambio,rewindno devuelve ningún valor (void).Simplicidad:
rewindsimplifica el control de errores al agrupar la limpieza de flags y el posicionamiento en un solo paso, eliminando la necesidad de escribir una cláusula condicionalif (fseek(...) != 0).
Glosario¶
- Stream (Flujo)
- Abstracción que representa un canal de comunicación para E/S.
- FILE
- Estructura de C que encapsula la información de control de un stream.
- EOF (End of File)
- Constante que representa el fin de un archivo.
- Buffer
- Área de almacenamiento temporal para optimizar transferencias de E/S.
Síntesis y Resumen¶
En este apunte se han presentado los conceptos fundamentales del tema.
Referencias y Lecturas Complementarias¶
Kernighan, B. W. y Ritchie, D. M. Kernighan & Ritchie, 2014. The C Programming Language (2.ª edición). Prentice Hall.
Consultá el Capítulo 7: Input and Output, donde se detalla el diseño de la biblioteca estándar de E/S, la manipulación de archivos y los descriptores del sistema.
King, K. N. King, 2008. C Programming: A Modern Approach (2.ª edición). W. W. Norton & Company.
Revisá el Capítulo 22: Input/Output para un estudio pormenorizado de los flujos de texto, las diferencias entre E/S con y sin buffer, y el manejo de errores con
feofyferror.
Gustedt, J. Gustedt, 2019. Modern C. Manning Publications.
Estudiá el Nivel 2 (Refine), sección de entrada/salida de la biblioteca estándar, donde se analiza la interacción de streams con el sistema operativo y la portabilidad del código.
- Kernighan, B. W., & Ritchie, D. M. (2014). C Programming Language, 2nd Edition.
- King, K. N. (2008). C Programming: A Modern Approach (2nd ed.). W. W. Norton & Company.
- Gustedt, J. (2019). Modern C. Manning Publications. https://modernc.gforge.inria.fr/