0x400Bh: No uses fflush(stdin); consumi el buffer con getchar()¶
Enunciado normativo¶
NO DEBE usarse
fflush(stdin); es comportamiento indefinido. Consumi el buffer congetchar()o lei confgets.
Síntoma en el código del estudiante¶
Después de un scanf aparece fflush(stdin) con el comentario mental de “limpiar
el búfer de entrada”. También suele verse fflush(stdin) antes de leer una
cadena con fgets, para descartar el salto de línea que quedó pendiente.
Diagnóstico¶
Mecanismo del defecto¶
fflush está pensado para flujos de salida: fuerza el vaciado del búfer
hacia el dispositivo. Su efecto sobre un flujo de entrada no está definido
por el estándar. En glibc, de hecho, descarta los bytes pendientes y parece
funcionar; en otras bibliotecas no hace nada, y en algunas puede fallar. El
programa que depende de fflush(stdin) es, por lo tanto, no portable.
El problema de fondo es no entender que scanf("%d", &x) deja en el búfer el
salto de línea u otro texto que no pudo convertir. La limpieza del búfer no se
hace con fflush, sino consumiendo explícitamente los caracteres sobrantes o
leyendo por líneas.
Consecuencia observable¶
En Linux con glibc el código suele “funcionar”, lo que oculta el defecto. Al
compilarlo en otra plataforma, fflush(stdin) no limpia y la lectura siguiente
—un %c o un fgets— recibe el salto de línea residual y avanza sola, con
resultados inesperados. A veces también se descarta entrada legítima que el
usuario tipeó de más.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §7.21.5.2 define fflush: si el flujo apunta a un archivo de
salida o de actualización cuya última operación fue de salida, escribe los datos
pendientes; para los demás casos, el comportamiento no está definido. La
entrada estándar cae en esa zona no definida. Depender de ella viola el
principio de escribir C portable y verificable.
Corrección idiomática¶
❌ Código con el antipatrón¶
int x;
printf("Ingrese un numero: ");
scanf("%d", &x);
fflush(stdin);
char nombre[32];
fgets(nombre, sizeof(nombre), stdin);Por qué es incorrecto: fflush(stdin) no está garantizado por el estándar. Si
no limpia, fgets lee el '\n' que quedó tras el número y devuelve una cadena
vacía.
✅ Código refactorizado¶
int x;
printf("Ingrese un numero: ");
if (scanf("%d", &x) != 1) {
return -1;
}
int c;
do {
c = getchar();
} while (c != '\n' && c != EOF);
char nombre[32];
if (fgets(nombre, sizeof(nombre), stdin) == NULL) {
return -1;
}El bucle do ... while consume explícitamente los caracteres que quedaron
pendientes hasta el fin de línea, sin depender de comportamiento indefinido. El
do respeta la delimitación obligatoria con llaves (0x100Eh: Delimitación obligatoria con bloque de llaves en lazos do-while) y evita la
asignación dentro de la condición (0x1012h: Prohibición de expresiones de asignación dentro de estructuras de control).
Errores típicos al compilar o ejecutar¶
No hay error de compilación. En entornos donde la limpieza no funciona, el efecto es que una lectura posterior se saltea.
Ingrese un numero: 42
Ingrese su nombre: [se saltea la lectura]En algunos compiladores con advertencias estrictas puede aparecer una alerta
sobre el uso de fflush sobre stdin.
warning: fflush on input stream is a common source of bugsChecklist de verificación¶
No uso
fflushsobrestdinni sobre ningún flujo de entrada.Consumo el salto de línea pendiente con
getcharen un bucle.Verifico el retorno de
scanfantes de continuar.Para leer cadenas, prefiero
fgetssobrescanf("%s").
Reglas relacionadas¶
0x4006h: Prohibición del antipatrón while (!feof(f)) para control de fin de archivo — regla asociada sobre el control de la E/S de
stdio.0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — verificar los retornos de lectura evita limpiar a ciegas.
0x5006h: Preferí fgets sobre gets y scanf para leer cadenas — preferir
fgetssobregets/scanfpara cadenas.0x1012h: Prohibición de expresiones de asignación dentro de estructuras de control — no anidar asignaciones dentro de la condición del lazo.