Regla 0x4009h: Prohibición de anidar llamadas a fopen() directamente dentro de funciones de E/S
Archivos y E/S (0x40XX)
0x4009h: Prohibición de anidar llamadas a fopen() directamente dentro de funciones de E/S¶
Enunciado normativo¶
NO DEBE pasarse el valor de retorno de
fopen(ofreopen) directamente como argumento de otra función de E/S. La apertura DEBE almacenarse en una variable para poder comprobarNULL, usarla y cerrarla.
fscanf(fopen(...), ...) no es una abreviación aceptable: es una apertura sin
control.
¿Por qué existe esta regla?¶
El problema¶
fopen retorna FILE * y puede retornar NULL. Cuando ese puntero se pasa
directamente como argumento, el valor se consume dentro de la expresión y el
programa pierde dos oportunidades: no puede comprobar si la apertura falló y no
conserva el puntero para cerrarla después.
El primer problema deriva en comportamiento indefinido: la función interna
recibe NULL y lo desreferencia. El segundo produce una fuga de recursos: el
flujo existe —si la apertura tuvo éxito— pero no hay forma de llamar a fclose
sobre él, porque el único puntero quedó enterrado en una expresión ya evaluada.
Aunque el programa no falle hoy, cada ejecución de esa línea deja un descriptor
abierto.
La regla es, en el fondo, una aplicación de la secuencialidad explícita: cada operación con nombre propio en su propia sentencia, para poder verificarla y reutilizarla (0x0002h: Una declaración de variable por línea).
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | Desreferenciar el NULL de un fopen fallido produce SIGSEGV. |
| Fuga de recursos | El flujo exitoso nunca se cierra porque su puntero se perdió. |
| Diagnóstico imposible | No hay variable sobre la cual reportar la ruta ni la causa. |
| Legibilidad | La expresión mezcla apertura y operación, ocultando dos efectos. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §7.21.5.3 condiciona la validez del flujo al retorno no nulo
de fopen, y §7.21.5.1 exige fclose para liberarlo. La cátedra prohíbe el
anidamiento porque hace imposible cumplir 0x4001h: Manejá correctamente la apertura y cierre de archivos y 0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción en la
misma sentencia, y porque el descriptor perdido es la causa más común de fallos
intermitentes por agotamiento de recursos.
Alcance y excepciones¶
Aplica a cualquier llamada de E/S que reciba un FILE *: fread, fwrite,
fgetc, fputc, fgets, fputs, fprintf, fscanf, fseek, ftell,
feof, fclose. Tampoco se admite fclose(fopen(...)), que abre y cierra sin
poder detectar el fallo de apertura ni el de cierre.
Excepción razonable: una función auxiliar puede encapsular la apertura y
retornar el FILE *; es correcta si el llamador lo guarda y lo verifica. La
prohibición es sobre el anidamiento en la expresión, no sobre las funciones que
devuelven flujos.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — fscanf con fopen anidado¶
fscanf(fopen("data.txt", "r"), "%d", &x);Por qué falla: si "data.txt" no existe, la función recibe NULL y lo
desreferencia. Si existe, el flujo queda abierto para siempre porque el puntero
no se guardó. No hay forma de saber cuál de los dos casos ocurrió.
❌ Contraejemplo 2 — fprintf y fclose anidados¶
fprintf(fopen("log.txt", "a"), "inicio\n");Por qué falla: además de la apertura sin verificar, el búfer puede no volcarse porque nadie cierra el flujo. El mensaje “inicio” quizá nunca llegue al archivo, y el descriptor filtrado se acumula en cada ejecución.
✅ Ejemplo conforme 1 — Variable, verificación, uso y cierre¶
#include <stdio.h>
FILE *f = fopen("data.txt", "r");
if (f == NULL) {
perror("data.txt");
return -1;
}
if (fscanf(f, "%d", &x) != 1) {
perror("fscanf");
}
fclose(f);Cada paso tiene su propia sentencia: se comprueba la apertura, se usa el flujo y
se cierra. El retorno de fscanf también se verifica (0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos).
✅ Ejemplo conforme 2 — Función constructora con verificación en el llamador¶
FILE *abrir_entrada(const char *ruta)
{
FILE *f = fopen(ruta, "r");
if (f == NULL) {
perror(ruta);
}
return f;
}
FILE *f = abrir_entrada("data.txt");
if (f == NULL) {
return -1;
}
int x;
if (fscanf(f, "%d", &x) == 1) {
procesar(x);
}
fclose(f);La apertura se encapsula, pero el puntero sigue siendo visible y verificable en el llamador, que además es el dueño responsable del cierre (ver 0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción).
⚠️ Casos límite¶
freopen: retornaFILE *y puede fallar; se aplica la misma prohibición y la misma verificación.fclose(fopen(...)): parece simétrico, pero pierde ambos diagnósticos y es igualmente una violación.Funciones envoltorio: válidas si devuelven el puntero; peliagudas si abren y no cierran (ver 0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción).
FILE *temporal: guardarlo en una variable local aunque se use una sola vez es obligatorio, no un lujo.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Regla 0x4009h: fopen/freopen dentro de otra llamada. |
grep | grep -n 'f.*(fopen' archivo.c | Apertura anidada en la lista de argumentos. |
cppcheck | cppcheck --enable=all archivo.c | resourceLeak / nullPointer en la expresión. |
Checklist de autocontrol¶
¿Guardé el
FILE *en una variable antes de usarlo?¿Comprobé
NULLinmediatamente después de abrir?¿Existe una única variable que conserva el puntero para el cierre?
¿Evité
fclose(fopen(...))y construcciones equivalentes?
Reglas relacionadas¶
0x4001h: Manejá correctamente la apertura y cierre de archivos — verificar la apertura exige un puntero almacenado.
0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción — la propiedad y el cierre requieren un puntero visible.
0x400Ah: Prohibición de operar sobre flujos de archivo tras haber invocado fclose() (use-after-close) — usar el flujo después de cerrarlo es el defecto simétrico.
0x0002h: Una declaración de variable por línea — una declaración y una sentencia por línea.