Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x400Ah: Prohibición de operar sobre flujos de archivo tras haber invocado fclose() (use-after-close)

Archivos y E/S (0x40XX)

Universidad Nacional de Río Negro

0x400Ah: Prohibición de operar sobre flujos de archivo tras haber invocado fclose() (use-after-close)

Enunciado normativo

NO DEBE usarse un FILE * después de haber invocado fclose sobre él. Inmediatamente después de cerrar, el puntero DEBE asignarse a NULL cuando siga estando en alcance.

fclose no es “pausar” el flujo: lo destruye. El puntero queda colgando y toda operación posterior es inválida.

¿Por qué existe esta regla?

El problema

fclose libera la estructura interna FILE asociada al flujo y desasocia el descriptor. A partir de ese momento, el valor del puntero deja de referirse a un objeto válido: es un puntero colgante (dangling pointer). Pasar ese puntero a cualquier función de stdio (fread, fgets, fseek, feof, un segundo fclose) es comportamiento indefinido.

La biblioteca no puede detectar el error de forma confiable: la memoria liberada puede reutilizarse para otra cosa y el puntero puede apuntar a datos que ya no tienen nada que ver con el flujo. En algunas plataformas la operación parece funcionar y en otras corrompe el estado del runtime; incluso puede provocar una violación de segmento dentro de la propia libc.

El segundo fclose merece mención aparte: se conoce como double free de flujos y corrompe las estructuras del asignador de stdio, con abortos inmediatos o daños diferidos. Anular el puntero después de cerrar convierte cualquier uso accidental en un fallo predecible y diagnosticable, porque fclose(NULL) es un error detectable y la desreferencia es evidente.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Comportamiento indefinidoLa operación sobre la estructura liberada produce resultados impredecibles.
Corrupción del runtimeUn double fclose daña las estructuras internas de stdio.
Violación de segmentoLa libc desreferencia memoria ya liberada y el proceso cae por SIGSEGV.
Bug dependiente de plataformaEl mismo código “funciona” en un equipo y falla en otro.

Fundamento en el estándar y en la cátedra

ISO/IEC 9899:2011 §7.21.5.1 define fclose como desasociación del flujo: una vez cerrado, el valor del puntero no puede usarse para acceder al objeto. La cátedra prohíbe el use-after-close por la misma razón que prohíbe usar memoria liberada (0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes): es el análogo de acceso a punteros colgantes en la gestión de flujos.

Alcance y excepciones

Aplica a todo uso de un FILE * tras fclose, incluidos los flujos estándar si el programa los cierra. También aplica a copias del puntero: si dos variables apuntan al mismo flujo y una lo cierra, la otra queda colgante.

No hay excepción: salvo volver a abrir explícitamente con fopen, no existe un uso válido del mismo valor después de cerrar.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Leer después de cerrar

FILE *f = fopen("log.txt", "r");
fclose(f);
fread(buf, 1, 10, f);

Por qué falla: fread recibe un puntero a una estructura FILE ya liberada. La operación es comportamiento indefinido; puede leer datos ajenos o provocar una caída, según cómo haya quedado la memoria del asignador.

❌ Contraejemplo 2 — Doble cierre y uso posterior

FILE *f = fopen("salida.txt", "w");
fclose(f);
fprintf(f, "fin\n");
fclose(f);

Por qué falla: el fprintf sobre el flujo cerrado y el segundo fclose operan sobre un objeto destruido. El doble cierre suele abortar el proceso al corromper las estructuras de stdio, y el alcance del daño es impredecible.

✅ Ejemplo conforme 1 — Cerrar y anular el puntero

FILE *f = fopen("log.txt", "r");
if (f == NULL) {
    return -1;
}
size_t leidos = fread(buf, 1, 10, f);
fclose(f);
f = NULL;

Tras el cierre, f se anula. Cualquier uso accidental posterior será un NULL detectable en lugar de un acceso a memoria liberada; el conteo de fread se conserva para verificación (0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos).

✅ Ejemplo conforme 2 — Una sola ventana de uso y un único cierre

int procesar(const char *ruta)
{
    FILE *f = fopen(ruta, "r");
    if (f == NULL) {
        return -1;
    }
    int estado = 0;
    char linea[128];
    while (fgets(linea, sizeof(linea), f) != NULL && estado == 0) {
        estado = analizar(linea);
    }
    fclose(f);
    return estado;
}

Todas las operaciones ocurren antes del cierre y existe un único fclose en el camino de salida; no queda ninguna ventana en la que el puntero pueda usarse después (ver 0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción).

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cRegla 0x400Ah: uso de FILE * tras fclose o doble cierre.
gccgcc -Wall -Wextra -std=c11 archivo.c-Wuse-after-free con optimizaciones en versiones recientes.
valgrindvalgrind --track-fds=yes ./programaAcceso a descriptor ya cerrado.
Revisión manualTodo fclose debe ir seguido de = NULL si el puntero sigue vivo.

Checklist de autocontrol

Reglas relacionadas