Gestión de archivos y flujos de datos en C

Para trabajar con archivos en C, no interactuamos directamente con el disco duro, sino que utilizamos un flujo (o stream). Un flujo es una abstracción que actúa como un puente entre tu programa y el archivo. Para gestionar este flujo, el estándar utiliza el tipo FILE* (un puntero a una estructura de archivo), que funciona como un “control remoto” u objeto opaco: tú no necesitas saber cómo se organiza internamente la estructura, solo usas sus funciones para leer o escribir.

Cuando abres un archivo con fopen, el sistema operativo te entrega este puntero. Si el archivo no existe (en modo lectura) o no tienes permisos, fopen devuelve NULL. Es fundamental verificar este retorno; si intentas usar un puntero NULL, el programa sufrirá un error de segmentación y colapsará.

Existen dos formas principales de manejar datos: el modo texto y el modo binario. En el modo texto (como "r" para leer o "w" para escribir), el sistema puede realizar transformaciones, como convertir los saltos de línea \n al formato específico del sistema operativo. En el modo binario (como "rb" o "wb"), los datos se tratan como bytes puros, sin transformaciones; esto es esencial cuando guardas estructuras de datos o imágenes.

¿Cuándo debes usar estas funciones? Siempre que necesites persistencia: guardar la configuración de un programa, registrar logs de error o leer bases de datos. Si no usas fclose para cerrar el flujo, dejas recursos abiertos en el sistema y, lo más peligroso, podrías perder los últimos datos escritos, ya que estos suelen guardarse en un búfer (una zona de memoria temporal) y solo se aseguran en el disco cuando el búfer se llena o se cierra el archivo.

#include <stdio.h>
#include <stdlib.h>

// Estructura para demostrar escritura/lectura binaria
typedef struct {
    int id;
    double valor;
} Registro;

int main(void) {
    const char *archivo_txt = "notas.txt";
    const char *archivo_bin = "datos.dat";

    // --- PARTE 1: Escritura de texto ---
    // "w" crea el archivo o trunca el existente (lo limpia)
    FILE *fp_txt = fopen(archivo_txt, "w");
    if (fp_txt == NULL) {
        perror("Error al abrir archivo de texto para escribir");
        return EXIT_FAILURE;
    }
    fprintf(fp_txt, "Primera linea de ejemplo\n");
    fprintf(fp_txt, "Segunda linea de ejemplo\n");
    fclose(fp_txt);

    // --- PARTE 2: Lectura, fseek y ftell ---
    fp_txt = fopen(archivo_txt, "r");
    if (fp_txt == NULL) {
        perror("Error al abrir archivo de texto para leer");
        return EXIT_FAILURE;
    }

    char linea[64];
    // fgets es más seguro que fscanf para texto porque limita la lectura
    while (fgets(linea, sizeof(linea), fp_txt) != NULL) {
        printf("Leído: %s", linea);
    }

    // ftell nos dice en qué byte estamos posicionados
    long pos_actual = ftell(fp_txt);
    printf("Posición tras leer todo: %ld bytes\n", pos_actual);

    // fseek nos permite saltar a una posición específica
    // SEEK_SET: inicio, SEEK_CUR: actual, SEEK_END: final
    if (fseek(fp_txt, 0, SEEK_SET) != 0) {
        perror("Error al mover el cursor");
    }
    
    // rewind es un atajo a fseek(fp, 0, SEEK_SET) y limpia errores
    rewind(fp_txt);
    printf("Archivo reiniciado al inicio.\n");
    fclose(fp_txt);

    // --- PARTE 3: Escritura y lectura binaria ---
    Registro reg_salida = {1024, 3.14159};
    
    // "wb" -> write binary
    FILE *fp_bin = fopen(archivo_bin, "wb");
    if (fp_bin == NULL) {
        perror("Error al abrir binario para escritura");
        return EXIT_FAILURE;
    }

    // Escribimos la estructura tal cual está en memoria
    size_t escritos = fwrite(&reg_salida, sizeof(Registro), 1, fp_bin);
    if (escritos != 1) {
        perror("Error al escribir datos binarios");
    }
    fclose(fp_bin);

    // "rb" -> read binary
    fp_bin = fopen(archivo_bin, "rb");
    if (fp_bin == NULL) {
        perror("Error al abrir binario para lectura");
        return EXIT_FAILURE;
    }

    Registro reg_entrada;
    // fread retorna el número de elementos leídos con éxito
    if (fread(&reg_entrada, sizeof(Registro), 1, fp_bin) == 1) {
        printf("Binario leído: ID=%d, Valor=%.5f\n", reg_entrada.id, reg_entrada.valor);
    } else {
        if (ferror(fp_bin)) {
            perror("Error durante la lectura binaria");
        }
    }

    if (feof(fp_bin)) {
        printf("Se alcanzó el fin del archivo binario.\n");
    }

    fclose(fp_bin);

    return EXIT_SUCCESS;
}

Desglose del código

En la Parte 1, usamos fopen con el modo "w". Si el archivo notas.txt ya existía, su contenido se habría borrado por completo (truncado). Usamos fprintf para escribir texto con formato, de forma similar a printf, pero dirigiendo la salida al flujo fp_txt.

En la Parte 2, fgets lee una línea completa (incluyendo el \n) y la guarda en linea. Es vital que el tamaño pasado sea sizeof(linea), ya que fgets reserva un espacio para el carácter nulo \0 y evita desbordamientos. ftell nos devuelve un long con el desplazamiento actual desde el principio del archivo. Usamos fseek y rewind para mover el cursor de lectura de vuelta al principio.

En la Parte 3, trabajamos con Registro. A diferencia de fprintf, fwrite no convierte los números a texto; simplemente copia los bytes de la memoria al archivo. Por eso, al leer con fread, debemos pasarle el mismo tamaño sizeof(Registro). Es fundamental comprobar que fread devuelve el número de elementos esperados (1 en este caso); si devuelve 0, algo falló o llegamos al fin del archivo (feof).

El error frecuente

Un error clásico es ignorar el valor de retorno de fopen. Fíjate en este patrón peligroso:

FILE *fp = fopen("archivo_inexistente.txt", "r");
// Si el archivo no existe, fp es NULL
char buf[10];
fgets(buf, 10, fp); // ¡CRASH! Intento de acceder a dirección 0 (NULL)

Además, muchos programadores olvidan que fclose también puede fallar, especialmente en sistemas de red o cuando el disco se llena durante el flush final de datos. Siempre es una buena práctica verificar que fclose se ejecute incluso en rutas de error para evitar fugas de recursos (resource leaks).

81

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio