Regla 0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo
Archivos y E/S (0x40XX)
0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo¶
Enunciado normativo¶
NO DEBE usarse
feof(f)niferror(f)como condición de un lazo de lectura. El lazo DEBE controlarse con el valor de retorno de la operación de lectura (fgets,fread,fscanf,fgetc).
La condición del lazo debe expresar “mientras la lectura tuvo éxito”, no “mientras el indicador de fin de archivo esté apagado”.
¿Por qué existe esta regla?¶
El problema¶
feof no anticipa el fin de archivo: lo reporta después. El indicador se
activa únicamente cuando una lectura intentó ir más allá del último dato y
falló. Por eso while (!feof(f)) es verdadero durante una iteración de más: la
lectura final devuelve NULL o cero elementos, pero el cuerpo del lazo se
ejecuta igual antes de volver a evaluar la condición.
El resultado clásico es que el último registro se procesa dos veces. Peor
aún, si la lectura no devuelve cero por EOF sino por un error real (por ejemplo,
un dispositivo que falla), feof nunca se activa y el lazo se vuelve infinito,
reprocesando el mismo contenido o basura indefinidamente. Para distinguir EOF
de error están feof y ferror, pero se consultan después de que la
lectura indicó un problema, no para decidir si leer.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Resultado incorrecto | El último registro se procesa dos veces (duplicado espurio). |
| Lazo infinito | Un error de lectura distinto de EOF no activa feof y el lazo no termina. |
| Datos basura | Se procesa un búfer sin actualizar en la iteración extra. |
| Dependencia frágil | El comportamiento depende de detalles del búfer de stdio, no del algoritmo. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §7.21.10.2 define feof, que retorna verdadero solo si el
indicador de fin de archivo está activo, y el indicador se activa al encontrarse
el fin durante una lectura. §7.21.10.3 define ferror como su contraparte de
error. La cátedra prohíbe el patrón porque viola el principio de controlar el
flujo con el resultado de la operación y porque produce errores silenciosos
difíciles de ver en pruebas con pocos datos.
Alcance y excepciones¶
Aplica a todo lazo de lectura de archivos, sin importar la función usada. No
alcanza al uso de feof/ferror después del lazo, que es correcto para
decidir si la terminación fue por fin de archivo o por error.
Excepción razonable: ningún uso de feof como cabecera de lazo es
aceptable. La forma correcta siempre existe y es más simple.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — fgets con feof en la cabecera¶
while (!feof(f)) {
fgets(buf, sizeof(buf), f);
procesar(buf);
}Por qué falla: en la última iteración fgets devuelve NULL y deja buf sin
modificar, pero procesar se ejecuta igual y vuelve a tratar el contenido
anterior. Es el antipatrón 0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo.
❌ Contraejemplo 2 — fread con feof y error real¶
while (!feof(f)) {
size_t n = fread(&elem, sizeof(elem), 1, f);
(void)n;
procesar(elem);
}Por qué falla: si fread falla por un error de E/S distinto de EOF, feof
nunca se activa y el lazo gira para siempre. Además el retorno se descarta, con
lo que ni siquiera se detecta el problema (0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos).
✅ Ejemplo conforme 1 — fgets controlado por su retorno¶
while (fgets(buf, sizeof(buf), f) != NULL) {
procesar(buf);
}Cada iteración procesa exactamente la línea que se acaba de leer; cuando se
agota el archivo, fgets devuelve NULL y el lazo termina sin una vuelta
extra.
✅ Ejemplo conforme 2 — fread controlado por su retorno y error aparte¶
while (fread(&elem, sizeof(elem), 1, f) == 1) {
procesar(elem);
}
if (ferror(f)) {
perror("fread");
}El lazo se ejecuta una vez por elemento leído. Al salir, feof y ferror
distinguen si terminó por fin de archivo o por error, y el diagnóstico se
reporta con perror (0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa).
⚠️ Casos límite¶
Última línea sin salto de línea:
fgetsla devuelve igual y el lazo la procesa una vez; conwhile (!feof)también, pero sin la garantía sobre el resto.Archivo vacío: la primera lectura falla y el lazo no entra; el patrón prohibido entraría una vez y procesaría basura.
freadque lee menos de un elemento: el conteo devuelto es 0; hay que distinguir EOF de error conferror.do ... while: leer primero y evaluar después también es válido, pero la condición debe seguir siendo el retorno de la lectura.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Regla 0x4006h: feof/ferror en la condición de un lazo. |
gcc | gcc -Wall -Wextra -std=c11 archivo.c | Sin señal directa; requiere revisión. |
| Revisión manual | — | Buscar while (!feof y while (!ferror en el código. |
Checklist de autocontrol¶
¿La condición del lazo es el retorno de la lectura?
¿Evité duplicar el último registro?
¿Distingo al salir si terminé por
feofo porferror?¿Reporto los errores de lectura con
perror/strerror?
Reglas relacionadas¶
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — la norma general de verificar retornos de E/S.
0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa — reportar el fallo detectado después del lazo.
0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción — el lazo de lectura no exime de cerrar el flujo.
0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas — elegir
forowhilesegún el tipo de iteración.
Antipatrón: Control de lectura con while(!feof())¶
Síntoma en el código del estudiante¶
Aparece un lazo de lectura cuya condición es while (!feof(f)) (o
while (!ferror(f))), y dentro del cuerpo se llama a fgets, fread o
fscanf sin usar su valor de retorno para controlar la iteración. El indicador
de fin de archivo se trata como si fuera una condición de corte anticipada.
Diagnóstico¶
Mecanismo del defecto¶
feof no predice el fin de archivo: lo confirma con retraso. El indicador
interno se activa cuando una lectura ya intentó pasar del último dato y
falló. Por eso, cuando while (!feof(f)) evalúa verdadero, todavía no se leyó
nada, y la iteración de más se ejecuta antes de que la condición vuelva a
evaluarse. En esa iteración extra la lectura devuelve NULL o cero elementos,
pero el cuerpo se ejecuta igual con el búfer en su estado anterior.
Además, feof es ajeno a los errores. Si la lectura falla por una causa distinta
del fin de archivo (un dispositivo que se desconecta, un sector dañado), el
indicador de EOF nunca se activa y el lazo no termina: se reprocesa el mismo
búfer indefinidamente.
Consecuencia observable¶
El síntoma clásico es que el último registro se procesa dos veces: se duplica la última línea, el último entero o el último elemento del arreglo. Con suficientes datos, además, el lazo puede no terminar cuando aparece un error de lectura, y la salida del programa deja de avanzar.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §7.21.10.2 define feof como verdadero solo si el indicador
de fin de archivo está activo, y ese indicador se activa al encontrarse el fin
durante una lectura. §7.21.7.2 establece que fgets retorna NULL ante EOF o
error, y §7.21.8.1 que fread retorna el número de elementos leídos, menor al
pedido ante error o fin de archivo. El retorno de la lectura, no feof, es la
señal de corte correcta.
Corrección idiomática¶
❌ Código con el antipatrón¶
while (!feof(f)) {
if (fgets(buf, sizeof(buf), f) != NULL) {
procesar(buf);
}
}Por qué es incorrecto: la guarda interna evita procesar el búfer nulo, pero el
lazo igual da una vuelta de más y la condición sigue dependiendo de feof.
Tampoco distingue si la última lectura falló por EOF o por error.
✅ Código refactorizado¶
while (fgets(buf, sizeof(buf), f) != NULL) {
procesar(buf);
}
if (ferror(f)) {
perror("fgets");
}La condición es el resultado de la lectura: cada iteración procesa exactamente
lo que se acaba de leer, sin vueltas extra. Al salir, ferror permite separar
un fin de archivo normal de un error real, que se reporta con perror.
Errores típicos al compilar o ejecutar¶
No hay error de compilación: el defecto es lógico y silencioso. La señal aparece en la salida del programa.
$ ./contar_lineas datos.txt
uno
dos
dos
Total: 3 lineas <-- datos.txt tenia solo 2 lineasCon un error de lectura, el síntoma cambia de duplicación a bloqueo.
$ ./leer_archivo disco_danado.bin
leyendo...
leyendo...
leyendo... <-- no termina: feof nunca se activaChecklist de verificación¶
La condición del lazo es el retorno de
fgets/fread/fscanf.No aparece
feofniferrorcomo cabecera de un lazo.El último registro se procesa una sola vez.
Al terminar, distingo EOF de error real con
feof/ferror.Los errores de lectura se reportan con
perror/strerror.
Reglas relacionadas¶
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — norma general de verificar los retornos de E/S.
0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo — prohibición explícita de
while (!feof(f)).0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa — reportar el error detectado después del lazo.
0x4004h: Mantené la simetría de recursos al abrir y cerrar archivos en el mismo nivel de abstracción — la lectura no exime de cerrar el flujo.