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 0x4001h: Manejá correctamente la apertura y cierre de archivos

Archivos y E/S (0x40XX)

Universidad Nacional de Río Negro

0x4001h: Manejá correctamente la apertura y cierre de archivos

Enunciado normativo

DEBE verificarse que el puntero devuelto por fopen no sea NULL antes de operar sobre el flujo. DEBE cerrarse todo flujo abierto con fclose en algún punto del programa.

Dos obligaciones simétricas: una en la apertura, otra en el cierre. Abrir sin verificar produce una caída inmediata; abrir sin cerrar, una fuga de descriptor que tarde o temprano rompe el proceso.

¿Por qué existe esta regla?

El problema

fopen es una operación mediada por el sistema operativo y puede fallar por causas ajenas al código: el archivo no existe, no hay permisos, la ruta es inválida o el proceso agotó sus descriptores. Ante cualquiera de esos casos retorna NULL, y en C NULL es una dirección inválida: toda función de E/S que reciba ese puntero (fread, fgets, fgetc, fprintf) intentará desreferenciarlo. El contrato de fopen es explícito: si no puede abrir, devuelve un puntero nulo, y comprobarlo es responsabilidad del llamador.

El cierre es la otra cara del mismo recurso. Cada fopen exitoso reserva un objeto FILE y un descriptor, y el proceso tiene un tope (ulimit -n). Un programa que abre en un lazo sin cerrar agota ese tope y hace fallar las aperturas siguientes aunque el archivo exista. Además, un flujo de escritura sin fclose puede quedar con su búfer sin volcar al disco.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Comportamiento indefinidoDesreferenciar un FILE * nulo en fread/fgets produce caída por SIGSEGV.
Bug silenciosoUn fopen fallido e ignorado deja que el programa lea basura o datos viejos del búfer.
Fuga de recursosCada fopen sin fclose consume un descriptor; tras N aperturas falla todo el proceso.
Datos no persistidosUn flujo de escritura sin fclose puede quedar con el búfer sin volcar al disco.

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

ISO/IEC 9899:2011 §7.21.5.3 establece que fopen retorna NULL si la apertura falla, y §7.21.5.1 define fclose como descarga y desasociación del flujo. La cátedra adopta la regla porque todas las demás reglas de 0x40XX presuponen un FILE * válido; sin esta comprobación ninguna otra defensa tiene efecto.

Alcance y excepciones

Aplica a toda apertura con fopen o freopen y a todo cierre con fclose, incluidos los flujos estándar cuando el programa decide cerrarlos.

Única excepción razonable: los flujos abiertos por el runtime (stdin, stdout, stderr) no requieren fclose explícito; el sistema los cierra al terminar el proceso.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Apertura sin verificar NULL

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

Por qué falla: si datos.txt no existe, f vale NULL y fread desreferencia una dirección prohibida. La caída ocurre en la lectura, lejos de la causa real, y el mensaje del sistema ni siquiera menciona la ruta intentada.

❌ Contraejemplo 2 — Verifica apertura pero filtra en el retorno anticipado

int procesar(const char *ruta)
{
    FILE *f = fopen(ruta, "r");
    if (f == NULL) {
        return -1;
    }
    if (ferror(f)) {
        return -2;
    }
    fclose(f);
    return 0;
}

Por qué falla: el return -2 abandona la función sin ejecutar fclose, así que el descriptor queda abierto. La apertura está validada, pero la simetría de recursos se rompe (ver 0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción).

✅ Ejemplo conforme 1 — Apertura validada y cierre simétrico

int leer_entero(const char *ruta, int *valor)
{
    FILE *f = fopen(ruta, "r");
    if (f == NULL) {
        return -1;
    }
    int leido = fscanf(f, "%d", valor);
    fclose(f);
    if (leido != 1) {
        return -2;
    }
    return 0;
}

El camino de error también cierra el flujo antes de retornar: ninguna rama escapa con f abierto, y el retorno de fscanf distingue una lectura correcta de un fallo (ver 0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos).

✅ Ejemplo conforme 2 — Apertura y cierre con guarda temprana

static void volcar(const char *ruta)
{
    FILE *f = fopen(ruta, "w");
    if (f == NULL) {
        perror(ruta);
        return;
    }
    fprintf(f, "hola\n");
    if (fclose(f) == EOF) {
        perror("fclose");
    }
}

La guarda temprana evita operar sobre NULL, perror conserva la causa (ver 0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa) y el cierre verificado detecta fallos de volcado (ver 0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura).

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cfopen sin comprobación de NULL o FILE * sin fclose.
gccgcc -Wall -Wextra -std=c11 archivo.c-Wunused-result cuando se ignora un retorno marcado.
cppcheckcppcheck --enable=all archivo.cnullPointer y resourceLeak.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Omisión de verificación de retorno NULL en fopen()

Síntoma en el código del estudiante

Se llama a fopen y, sin comprobar el resultado, se usa el puntero de inmediato en fread, fgets, fgetc, fprintf o fclose. También aparece la apertura anidada dentro de otra llamada de E/S, donde la verificación es directamente imposible (ver error típico).

Diagnóstico

Mecanismo del defecto

fopen retorna un puntero a FILE si la apertura tuvo éxito y NULL si falló. Las causas de fallo son variadas: el archivo no existe, la ruta es inválida, faltan permisos, el proceso agotó sus descriptores o el medio de almacenamiento dio error. El programador no controla la mayoría de esas condiciones.

En C, NULL es una dirección inválida. Las funciones de stdio no verifican que el puntero que reciben sea válido: asumen que el llamador cumplió el contrato. Entonces, al ejecutar fread o fgets con NULL, la biblioteca intenta acceder a la estructura interna del flujo en la dirección 0 y el proceso recibe una violación de segmento. El defecto no es un resultado incorrecto: es una caída.

Consecuencia observable

El programa termina abruptamente con Segmentation fault (core dumped) en la primera operación de E/S, sin haber reportado la causa real (el archivo faltante, el permiso denegado). El mensaje de caída no menciona la ruta ni el motivo, por lo que el diagnóstico queda a cargo del programador.

Fundamento en el estándar C11

ISO/IEC 9899:2011 §7.21.5.3 define fopen y establece que retorna NULL si la apertura falla. La validez de todas las operaciones posteriores depende de un retorno no nulo; desreferenciar un puntero nulo es comportamiento indefinido (§6.5.3.2). La cátedra exige la comprobación inmediata porque toda la categoría 0x40XX presupone un flujo válido.

Corrección idiomática

❌ Código con el antipatrón
FILE *f = fopen("datos.txt", "r");
fread(&elem, sizeof(elem), 1, f);
fclose(f);

Por qué es incorrecto: si datos.txt no existe, f es NULL y fread desreferencia la dirección 0. El programa cae en la lectura y el usuario nunca se entera de que el archivo faltaba.

✅ Código refactorizado
#include <stdio.h>

FILE *f = fopen("datos.txt", "r");
if (f == NULL) {
    perror("datos.txt");
    return -1;
}
if (fread(&elem, sizeof(elem), 1, f) != 1) {
    perror("fread");
}
fclose(f);

La comprobación inmediata evita la desreferencia, perror informa la causa del sistema (0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa), el retorno de fread se verifica (0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos) y el flujo se cierra en el mismo nivel en que se abrió (0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción).

Errores típicos al compilar o ejecutar

No hay error de compilación obligatorio. Con optimizaciones y advertencias estrictas puede aparecer una alerta de posible desreferencia nula.

warning: null pointer dereference [-Wnull-dereference]

En ejecución, la caída es inmediata.

$ ./leer
Segmentation fault (core dumped)

Si la apertura se anidó en otra llamada, el compilador directamente no puede avisar.

$ ./cargar
Segmentation fault (core dumped)

Checklist de verificación

Reglas relacionadas