¿Por qué “while (! feof (archivo))” siempre es incorrecto?

10 minutos de lectura

avatar de usuario
Guillermo Pursell

¿Qué hay de malo en usar feof() controlar un bucle de lectura? Por ejemplo:

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char **argv)
{
    char *path = "stdin";
    FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin;

    if( fp == NULL ){
        perror(path);
        return EXIT_FAILURE;
    }

    while( !feof(fp) ){  /* THIS IS WRONG */
        /* Read and process data from file… */
    }
    if( fclose(fp) != 0 ){
        perror(path);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

¿Qué tiene de malo este bucle?

avatar de usuario
Guillermo Pursell

Está mal porque (en ausencia de un error de lectura) entra en el bucle una vez más de lo que espera el autor. Si hay un error de lectura, el ciclo nunca termina.

Considere el siguiente código:

/* WARNING: demonstration of bad coding technique!! */

#include <stdio.h>
#include <stdlib.h>

FILE *Fopen(const char *path, const char *mode);

int main(int argc, char **argv)
{
    FILE *in;
    unsigned count;

    in = argc > 1 ? Fopen(argv[1], "r") : stdin;
    count = 0;

    /* WARNING: this is a bug */
    while( !feof(in) ) {  /* This is WRONG! */
        fgetc(in);
        count++;
    }
    printf("Number of characters read: %u\n", count);
    return EXIT_SUCCESS;
}

FILE * Fopen(const char *path, const char *mode)
{
    FILE *f = fopen(path, mode);
    if( f == NULL ) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    return f;
}

Este programa imprimirá constantemente uno mayor que el número de caracteres en el flujo de entrada (suponiendo que no haya errores de lectura). Considere el caso en el que el flujo de entrada está vacío:

$ ./a.out < /dev/null
Number of characters read: 1

En este caso, feof() se llama antes de que se hayan leído los datos, por lo que devuelve falso. Se entra en el bucle, fgetc() es llamado (y regresa EOF), y el conteo se incrementa. Entonces feof() se llama y devuelve verdadero, lo que hace que el ciclo se anule.

Esto sucede en todos estos casos. feof() no devuelve verdadero hasta después una lectura en la secuencia encuentra el final del archivo. El propósito de feof() NO es verificar si la próxima lectura llegará al final del archivo. El propósito de feof() es determinar el estado de una función de lectura anterior y distinguir entre una condición de error y el final del flujo de datos. Si fread() devuelve 0, debe usar feof/ferror para decidir si se produjo un error o si se consumieron todos los datos. Del mismo modo si fgetc devoluciones EOF. feof() solo es util después fread ha devuelto cero o fgetc ha regresado EOF. Antes de que eso suceda, feof() siempre devolverá 0.

Siempre es necesario comprobar el valor de retorno de una lectura (ya sea un fread()o un fscanf()o un fgetc()) antes de llamar feof().

Peor aún, considere el caso en el que se produce un error de lectura. En ese caso, fgetc() devoluciones EOF, feof() devuelve falso y el ciclo nunca termina. En todos los casos en que while(!feof(p)) se utiliza, debe haber al menos un control dentro del bucle para ferror()o al menos la condición while debería ser reemplazada por while(!feof(p) && !ferror(p)) o existe una posibilidad muy real de un bucle infinito, probablemente arrojando todo tipo de basura a medida que se procesan datos no válidos.

Entonces, en resumen, aunque no puedo afirmar con certeza que nunca hay una situación en la que pueda ser semánticamente correcto escribir “while(!feof(f))“(aunque hay deber ser otra verificación dentro del bucle con un descanso para evitar un bucle infinito en un error de lectura), se da el caso de que casi siempre es erróneo. E incluso si alguna vez surgiera un caso en el que sería correcto, es tan idiomáticamente incorrecto que no sería la forma correcta de escribir el código. Cualquiera que vea ese código debería dudar inmediatamente y decir: “eso es un error”. Y posiblemente abofetear al autor (a menos que el autor sea su jefe, en cuyo caso se recomienda discreción).

  • Debe agregar un ejemplo de código correcto, ya que imagino que mucha gente vendrá aquí en busca de una solución rápida.

    – jleahy

    12 de julio de 2013 a las 16:27

  • ¿Es esto diferente de file.eof()?

    – Tomás

    26 de agosto de 2014 a las 22:48

  • @Thomas: no soy un experto en C++, pero creo que file.eof() devuelve efectivamente el mismo resultado que feof(file) || ferror(file), entonces es muy diferente. Pero esta pregunta no pretende ser aplicable a C++.

    – William Pursell

    26 de agosto de 2014 a las 23:36

  • @ m-ric eso tampoco es correcto, porque aún intentará procesar una lectura que falló.

    – Mark Ransom

    09/04/2015 a las 18:25

  • esta es la respuesta correcta real. feof() se usa para conocer el resultado del intento de lectura anterior. Por lo tanto, probablemente no quiera usarlo como su condición de interrupción de bucle. +1

    – Jacobo

    28 de enero de 2017 a las 16:06

  • printf("%c", fgetc(in));? Eso es un comportamiento indefinido. fgetc() devoluciones intno char.

    –Andrew Henle

    8 de junio de 2020 a las 0:25

  • @AndrewHenle ¡Tienes razón! Cambiando char c para int c ¡obras! ¡¡Gracias!!

    –Scott Deagan

    9 de junio de 2020 a las 10:12

  • El primer ejemplo hace no trabajar de forma fiable al leer desde un archivo de texto. Si alguna vez encuentra un error de lectura, el proceso se atascará en un bucle infinito con c constantemente configurado en EOF y feof devolviendo falso constantemente.

    – William Pursell

    29 de julio de 2020 a las 15:01

  • @AndrewHenle ¿Qué parte de "%c" espera un inty no un char, es difícil de entender? Lea la página de manual o el estándar C, cualquiera de ellos.

    – 12431234123412341234123

    2 oct 2020 a las 20:02

  • @AndrewHenle: Ni siquiera es posible aprobar un char argumento a printfporque un argumento de tipo char será Ser promovido a una int de todas formas.

    – Andreas Wenzel

    6 de noviembre de 2020 a las 7:25


avatar de usuario
Un programador

feof() indica si se ha intentado leer más allá del final del archivo. Eso significa que tiene poco efecto predictivo: si es cierto, está seguro de que la siguiente operación de entrada fallará (por cierto, no está seguro de que la anterior haya fallado), pero si es falsa, no está seguro de la próxima entrada. la operación tendrá éxito. Además, las operaciones de entrada pueden fallar por otras razones además del final del archivo (un error de formato para la entrada formateada, una falla pura de E/S (falla del disco, tiempo de espera de la red) para todos los tipos de entrada), por lo que incluso si pudiera predecir el final del archivo (y cualquiera que haya intentado implementar Ada one, que es predictivo, le dirá que puede ser complejo si necesita omitir espacios y que tiene efectos no deseados en los dispositivos interactivos, a veces forzando la entrada del siguiente línea antes de iniciar el manejo de la anterior), tendría que ser capaz de manejar una falla.

Entonces, el modismo correcto en C es hacer un bucle con el éxito de la operación IO como condición de bucle y luego probar la causa de la falla. Por ejemplo:

while (fgets(line, sizeof(line), file)) {
    /* note that fgets don't strip the terminating \n, checking its
       presence allow to handle lines longer that sizeof(line), not showed here */
    ...
}
if (ferror(file)) {
   /* IO failure */
} else if (feof(file)) {
   /* format error (not possible with fgets, but would be with fscanf) or end of file */
} else {
   /* format error (not possible with fgets, but would be with fscanf) */
}

  • Llegar al final de un archivo no es un error, por lo que cuestiono la frase “las operaciones de entrada pueden fallar por otras razones además del final del archivo”.

    – William Pursell

    29 de septiembre de 2012 a las 12:59

  • @WilliamPursell, alcanzar el eof no es necesariamente un error, pero no poder realizar una operación de entrada debido a eof es uno. Y es imposible en C detectar de forma fiable el eof sin haber hecho que falle una operación de entrada.

    – Un programador

    29 de septiembre de 2012 a las 15:12

  • De acuerdo último else no es posible con sizeof(line) >= 2 y fgets(line, sizeof(line), file) pero posible con patología size <= 0 y fgets(line, size, file). Tal vez incluso posible con sizeof(line) == 1.

    – chux – Reincorporar a Monica

    26/03/2015 a las 21:21

  • Toda esa charla sobre el “valor predictivo”… Nunca lo pensé de esa manera. En mi mundo, feof(f) no PREDICE nada. Indica que una operación ANTERIOR ha llegado al final del archivo. Nada más y nada menos. Y si no hubo una operación anterior (solo lo abrió), no informa el final del archivo, incluso si el archivo estaba vacío para empezar. Entonces, aparte de la explicación de concurrencia en otra respuesta anterior, no creo que haya ninguna razón para no repetir feof(f).

    – BitTickler

    24/09/2017 a las 20:36

  • @AProgrammer: una solicitud de “leer hasta N bytes” que arroja cero, ya sea debido a un EOF “permanente” o porque no hay más datos disponibles aún, no es un error. Si bien feof() puede no predecir de manera confiable que las solicitudes futuras generarán datos, puede indicar de manera confiable que las solicitudes futuras no. Tal vez debería haber una función de estado que indicara “Es plausible que las futuras solicitudes de lectura tengan éxito”, con una semántica que después de leer hasta el final de un archivo ordinario, una implementación de calidad debería decir que es poco probable que las lecturas futuras tengan éxito. en ausencia de alguna razón para creer que podrían.

    – Super gato

    3 de febrero de 2019 a las 15:49


No, no siempre está mal. Si su condición de bucle es “mientras no hemos intentado leer más allá del final del archivo”, entonces usa while (!feof(f)). Sin embargo, esta no es una condición de bucle común; por lo general, desea probar otra cosa (como “¿puedo leer más”). while (!feof(f)) no está mal, es solo utilizado equivocado.

  • Llegar al final de un archivo no es un error, por lo que cuestiono la frase “las operaciones de entrada pueden fallar por otras razones además del final del archivo”.

    – William Pursell

    29 de septiembre de 2012 a las 12:59

  • @WilliamPursell, alcanzar el eof no es necesariamente un error, pero no poder realizar una operación de entrada debido a eof es uno. Y es imposible en C detectar de forma fiable el eof sin haber hecho que falle una operación de entrada.

    – Un programador

    29 de septiembre de 2012 a las 15:12

  • De acuerdo último else no es posible con sizeof(line) >= 2 y fgets(line, sizeof(line), file) pero posible con patología size <= 0 y fgets(line, size, file). Tal vez incluso posible con sizeof(line) == 1.

    – chux – Reincorporar a Monica

    26/03/2015 a las 21:21

  • Toda esa charla sobre el “valor predictivo”… Nunca lo pensé de esa manera. En mi mundo, feof(f) no PREDICE nada. Indica que una operación ANTERIOR ha llegado al final del archivo. Nada más y nada menos. Y si no hubo una operación anterior (solo lo abrió), no informa el final del archivo, incluso si el archivo estaba vacío para empezar. Entonces, aparte de la explicación de concurrencia en otra respuesta anterior, no creo que haya ninguna razón para no repetir feof(f).

    – BitTickler

    24/09/2017 a las 20:36

  • @AProgrammer: una solicitud de “leer hasta N bytes” que arroja cero, ya sea debido a un EOF “permanente” o porque no hay más datos disponibles aún, no es un error. Si bien feof() puede no predecir de manera confiable que las solicitudes futuras generarán datos, puede indicar de manera confiable que las solicitudes futuras no. Tal vez debería haber una función de estado que indicara “Es plausible que las futuras solicitudes de lectura tengan éxito”, con una semántica que después de leer hasta el final de un archivo ordinario, una implementación de calidad debería decir que es poco probable que las lecturas futuras tengan éxito. en ausencia de alguna razón para creer que podrían.

    – Super gato

    3 de febrero de 2019 a las 15:49


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad