Regla 0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos
Archivos y E/S (0x40XX)
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos¶
Enunciado normativo¶
DEBE comprobarse el valor de retorno de toda operación de E/S (
fread,fwrite,fgetc,fgets,fprintf,fscanf) antes de asumir que la transferencia se completó.
El retorno no es decorativo: es el único canal por el que la biblioteca informa cuántos elementos se transfirieron, si hubo error y si se llegó al fin de archivo.
¿Por qué existe esta regla?¶
El problema¶
Las funciones de E/S de C no lanzan excepciones ni abortan el programa ante un
error: informan por valor de retorno. fread devuelve la cantidad de
elementos efectivamente leídos, que puede ser menor a la pedida; fgetc
devuelve EOF; fgets devuelve NULL; fprintf devuelve un valor negativo si
falla la escritura.
Un archivo puede terminar antes de lo previsto, un dispositivo puede fallar a
mitad de una transferencia o el disco puede llenarse. Si el programa ignora el
retorno, sigue usando un búfer que nunca se llenó, con datos viejos o basura, y
el defecto se propaga silenciosamente. En escritura, un retorno negativo
ignorado hace creer que los datos se guardaron cuando en realidad se perdieron.
En E/S binaria una lectura corta puede ser legítima (último bloque), pero debe
distinguirse de un error real; para eso están el conteo devuelto y ferror.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Bug silencioso | Un búfer parcialmente leído se procesa como si estuviera completo. |
| Datos corruptos | Un fwrite fallido ignorado produce archivos truncados o incompletos. |
| Lazo infinito | Si una lectura falla sin marcar EOF y no se mira el retorno, el lazo gira sin avanzar. |
| Diagnóstico perdido | Sin mirar el retorno no se sabe si el problema fue EOF, error de disco o permisos. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §7.21.8.1 define que fread retorna el número de elementos
leídos, menor al pedido solo si ocurre un error o el fin de archivo.
§7.21.7.2 fija que fgets retorna NULL ante EOF o error y §7.21.6.4 que
fprintf retorna negativo si falla la salida. La cátedra adopta la regla como
base de la E/S robusta: sin retornos verificados, ni 0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa ni
0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura pueden funcionar.
Alcance y excepciones¶
Aplica a las funciones de <stdio.h>: fread, fwrite, fgetc, fgets,
fputc, fputs, fprintf, fscanf y fflush. No alcanza a printf/scanf
sobre consola cuando el flujo es interactivo y el resultado no compromete datos
persistentes, aunque verificarlos sigue siendo buena práctica.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Lectura única sin verificar el conteo¶
while (!feof(f)) {
fread(&elem, sizeof(elem), 1, f);
procesar(elem);
}Por qué falla: feof no se vuelve verdadero hasta que una lectura previa ya
falló. El último elemento se procesa dos veces y, si fread leyó cero por un
error distinto de EOF, procesar trabaja sobre datos viejos. Es el antipatrón
canónico 0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo.
❌ Contraejemplo 2 — Escritura cuyo retorno negativo se descarta¶
void guardar_todo(FILE *f, const int *valores, size_t n)
{
for (size_t i = 0; i < n; i++) {
fprintf(f, "%d\n", valores[i]);
}
}Por qué falla: si el disco se llena a mitad del lazo, fprintf devuelve un
valor negativo y las líneas restantes no se escriben. El llamador no recibe
señal alguna y el archivo queda truncado sin que nadie lo note (ver
0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura).
✅ Ejemplo conforme 1 — Lazo controlado por el retorno¶
int sumar_enteros(const char *ruta, long *total)
{
FILE *f = fopen(ruta, "r");
if (f == NULL) {
return -1;
}
*total = 0;
int x;
int leidos = 0;
while (fscanf(f, "%d", &x) == 1) {
*total += x;
leidos++;
}
fclose(f);
return leidos;
}El lazo termina exactamente cuando fscanf deja de convertir un entero, sin
duplicar el último dato y sin depender de feof (ver 0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo).
✅ Ejemplo conforme 2 — Escritura verificada elemento por elemento¶
int escribir_bloque(FILE *f, const unsigned char *datos, size_t n)
{
size_t escritos = fwrite(datos, 1, n, f);
if (escritos != n) {
return -1;
}
return 0;
}Comparar escritos con n detecta tanto un error de disco como una
transferencia parcial; el llamador puede decidir reintentar o abortar.
⚠️ Casos límite¶
Lectura corta legítima:
freadpuede devolver menos de lo pedido en el último bloque; distinguí “corto por EOF” de “corto por fallo” conferror.fgetc: retornaint, nochar; guardá el resultado en unintpara no confundir el byte0xFFcon el fin de archivo.fwriteconn == 0: retorna0y no es un fallo; el chequeoescritos != nno debe dispararse en ese caso.ferrorno se limpia solo: una vez activado permanece hastaclearerr.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Retorno de E/S descartado o feof en la condición. |
gcc | gcc -Wall -Wextra -std=c11 archivo.c | warn_unused_result en funciones anotadas. |
cppcheck | cppcheck --enable=all archivo.c | ignoredReturnValue. |
Checklist de autocontrol¶
¿Guardé el retorno de cada lectura y lo comparé con el valor esperado?
¿Controlé el lazo con el retorno de la lectura y no con
feof?¿Traté la lectura corta por EOF de forma distinta de la de error?
¿Verifiqué que
fwrite/fprintfescribieron todo lo pretendido?
Reglas relacionadas¶
0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo — prohibición explícita del lazo
while (!feof(f)).0x4003h: Utilizá errno, perror y strerror para reportar fallos del sistema operativo de manera precisa — reportar el error detectado con
errnoyperror.0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura — el retorno de
fclosecierra la última ventana de error.0x4001h: Manejá correctamente la apertura y cierre de archivos — verificar retornos presupone un
FILE *válido.