0x4001h: Manejá correctamente la apertura y cierre de archivos¶
Enunciado normativo¶
DEBE verificarse que el puntero devuelto por
fopenno seaNULLantes de operar sobre el flujo. DEBE cerrarse todo flujo abierto confcloseen 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 consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | Desreferenciar un FILE * nulo en fread/fgets produce caída por SIGSEGV. |
| Bug silencioso | Un fopen fallido e ignorado deja que el programa lea basura o datos viejos del búfer. |
| Fuga de recursos | Cada fopen sin fclose consume un descriptor; tras N aperturas falla todo el proceso. |
| Datos no persistidos | Un 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¶
fopenembebido en la llamada de E/S:fscanf(fopen(...), ...)impide comprobarNULLy pierde el puntero para cerrar (ver 0x4009h: Prohibición de anidar llamadas a fopen() directamente dentro de funciones de E/S).freopen: también retornaNULLal fallar; no se debe asumir que el flujo previo sigue siendo válido.Cierre de
stdout: conviene comprobar el retorno porque el búfer puede no volcarse (ver 0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | fopen sin comprobación de NULL o FILE * sin fclose. |
gcc | gcc -Wall -Wextra -std=c11 archivo.c | -Wunused-result cuando se ignora un retorno marcado. |
cppcheck | cppcheck --enable=all archivo.c | nullPointer y resourceLeak. |
Checklist de autocontrol¶
¿Comprobé
if (f == NULL)inmediatamente después de cadafopen?¿Reporté el motivo con
perrorostrerroren lugar de un mensaje mudo?¿Todo camino de salida, incluidos los
returnanticipados, cierra el flujo?¿Cerré el flujo en el mismo nivel de abstracción en que lo abrí?
Reglas relacionadas¶
0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción — la simetría de recursos exige que quien abre sea quien cierra.
0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa — usar
perrorostrerrorpara diagnosticar el fallo.0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura — validar el retorno de
fcloseen flujos de escritura.0x4009h: Prohibición de anidar llamadas a fopen() directamente dentro de funciones de E/S — no anidar
fopendentro de otra llamada de E/S.
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¶
¿Comprobé
if (f == NULL)inmediatamente después de cadafopen?¿Reporté la causa con
perrorostrerror?¿Evité anidar
fopendentro de otra función de E/S?¿Cierro el flujo con
fcloseen el mismo nivel en que lo abrí?
Reglas relacionadas¶
0x4001h: Manejá correctamente la apertura y cierre de archivos — regla asociada: verificar la apertura y cerrar el flujo.
0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa —
perror/strerrorpara diagnosticar el fallo de apertura.0x4009h: Prohibición de anidar llamadas a fopen() directamente dentro de funciones de E/S — prohibición de anidar
fopenen funciones de E/S.0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción — la propiedad y el cierre simétrico del recurso.