Últimamente he visto personas tratando de leer archivos como este en muchas publicaciones:
#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?
feof()
para controlar un bucleRespuestas:
Me gustaría proporcionar una perspectiva abstracta de alto nivel.
Concurrencia y simultaneidad
Las operaciones de E / S interactúan con el entorno. El entorno no es parte de su programa y no está bajo su control. El entorno realmente existe "simultáneamente" con su programa. Al igual que con todas las cosas concurrentes, las preguntas sobre el "estado actual" no tienen sentido: no existe un concepto de "simultaneidad" entre los eventos concurrentes. Muchas propiedades del estado simplemente no existen al mismo tiempo.
Permítame hacer esto más preciso: suponga que quiere preguntar, "¿tiene más datos"? Puede pedir esto a un contenedor concurrente o a su sistema de E / S. Pero la respuesta generalmente es inaccesible y, por lo tanto, no tiene sentido. Entonces, ¿qué pasa si el contenedor dice "sí"? En el momento en que intente leer, es posible que ya no tenga datos. Del mismo modo, si la respuesta es "no", para el momento en que intente leer, los datos pueden haber llegado. La conclusión es que simplemente hayninguna propiedad como "Tengo datos", ya que no puede actuar de manera significativa en respuesta a cualquier respuesta posible. (La situación es un poco mejor con la entrada almacenada en el búfer, donde posiblemente podría obtener un "sí, tengo datos" que constituye algún tipo de garantía, pero aún tendría que ser capaz de lidiar con el caso contrario. Y con la salida de la situación ciertamente es tan malo como lo describí: nunca se sabe si ese disco o ese búfer de red está lleno).
Así llegamos a la conclusión de que es imposible, y de hecho la ONU razonable , para pedir un sistema de E / S si será capaz de realizar una operación de E / S. La única forma posible de interactuar con él (al igual que con un contenedor concurrente) es intentar la operación y verificar si tuvo éxito o falló. En ese momento donde interactúa con el entorno, entonces y solo entonces puede saber si la interacción fue realmente posible, y en ese punto debe comprometerse a realizar la interacción. (Este es un "punto de sincronización", por así decirlo).
EOF
Ahora llegamos a EOF. EOF es la respuesta que obtiene de un intento de operación de E / S. Significa que estaba intentando leer o escribir algo, pero al hacerlo no pudo leer o escribir ningún dato, y en su lugar se encontró el final de la entrada o salida. Esto es cierto para esencialmente todas las API de E / S, ya sea la biblioteca estándar de C, iostreams de C ++ u otras bibliotecas. Mientras las operaciones de E / S tengan éxito, simplemente no puede saber si las futuras operaciones tendrán éxito. Siempre debe intentar primero la operación y luego responder al éxito o al fracaso.
Ejemplos
En cada uno de los ejemplos, tenga en cuenta cuidadosamente que primero intentamos la operación de E / S y luego consumimos el resultado si es válido. Tenga en cuenta además que siempre debemos usar el resultado de la operación de E / S, aunque el resultado toma diferentes formas y formas en cada ejemplo.
C stdio, leído de un archivo:
El resultado que debemos usar es
n
el número de elementos que se leyeron (que puede ser tan pequeño como cero).C Stdio,
scanf
:El resultado que debemos usar es el valor de retorno de
scanf
, el número de elementos convertidos.C ++, extracción con formato iostreams:
El resultado que debemos usar es en
std::cin
sí mismo, que se puede evaluar en un contexto booleano y nos dice si la secuencia todavía está en elgood()
estado.C ++, getline de iostreams:
El resultado que debemos usar es nuevamente
std::cin
, como antes.POSIX,
write(2)
para vaciar un búfer:El resultado que usamos aquí es
k
el número de bytes escritos. El punto aquí es que solo podemos saber cuántos bytes se escribieron después de la operación de escritura.POSIX
getline()
El resultado que debemos usar es
nbytes
el número de bytes hasta la nueva línea (o EOF si el archivo no terminó con una nueva línea).Tenga en cuenta que la función retorna explícitamente
-1
(¡y no EOF!) Cuando ocurre un error o llega a EOF.Puede notar que rara vez deletreamos la palabra real "EOF". Por lo general, detectamos la condición de error de alguna otra manera que nos interesa más inmediatamente (p. Ej., No realizar tantas E / S como hubiéramos deseado). En cada ejemplo, hay alguna característica de API que podría decirnos explícitamente que se ha encontrado el estado EOF, pero esta no es una información terriblemente útil. Es mucho más un detalle de lo que a menudo nos importa. Lo que importa es si la E / S tuvo éxito, más de lo que falló.
Un último ejemplo que realmente consulta el estado EOF: suponga que tiene una cadena y desea probar que representa un entero en su totalidad, sin bits adicionales al final, excepto los espacios en blanco. Usando C ++ iostreams, es así:
Usamos dos resultados aquí. El primero es
iss
, el objeto de flujo en sí mismo, para comprobar que la extracción formateada se realizó correctamentevalue
. Pero luego, después de consumir también espacios en blanco, realizamos otra operación de E / S /iss.get()
y esperamos que falle como EOF, que es el caso si toda la cadena ya ha sido consumida por la extracción formateada.En la biblioteca estándar de C, puede lograr algo similar con las
strto*l
funciones al verificar que el puntero final haya llegado al final de la cadena de entrada.La respuesta
while(!feof)
está mal porque prueba algo que es irrelevante y no prueba algo que necesita saber. El resultado es que está ejecutando código erróneamente que supone que está accediendo a datos que se leyeron con éxito, cuando en realidad esto nunca sucedió.fuente
feof()
no "pregunta al sistema de E / S si tiene más datos".feof()
, de acuerdo con la página de manual (Linux) : "prueba el indicador de fin de archivo para la secuencia a la que apunta la secuencia, devolviendo un valor distinto de cero si está configurado". (también, una llamada explícita aclearerr()
es la única forma de restablecer este indicador); A este respecto, la respuesta de William Pursell es mucho mejor.Está mal porque (en ausencia de un error de lectura) ingresa al bucle una vez más de lo que el autor espera. Si hay un error de lectura, el ciclo nunca termina.
Considere el siguiente código:
Este programa imprimirá uno mayor que el número de caracteres en la secuencia de entrada (suponiendo que no haya errores de lectura). Considere el caso donde el flujo de entrada está vacío:
En este caso,
feof()
se llama antes de que se haya leído ningún dato, por lo que devuelve falso. Se ingresa el ciclo,fgetc()
se llama (y se devuelveEOF
), y se incrementa el conteo. Luegofeof()
se llama y devuelve verdadero, lo que hace que el ciclo se cancele.Esto sucede en todos estos casos.
feof()
no devuelve verdadero hasta después de que una lectura en la secuencia encuentra el final del archivo. El propósito defeof()
NO es verificar si la próxima lectura llegará al final del archivo. El propósito defeof()
es distinguir entre un error de lectura y haber llegado al final del archivo. Sifread()
devuelve 0, debe usarfeof
/ferror
para decidir si se encontró un error o si se consumieron todos los datos. Del mismo modo sifgetc
vuelveEOF
.feof()
solo es útil después de que fread haya devuelto cero ofgetc
haya regresadoEOF
. Antes de que eso suceda,feof()
siempre devolverá 0.Siempre es necesario verificar el valor de retorno de una lectura (ya sea una
fread()
, o unafscanf()
, o unafgetc()
) antes de llamarfeof()
.Peor aún, considere el caso donde ocurre un error de lectura. En ese caso,
fgetc()
devuelveEOF
,feof()
devuelve falso y el ciclo nunca termina. En todos los casos en quewhile(!feof(p))
se usa, debe haber al menos una verificación dentro del bucleferror()
, o al menos la condición while debe reemplazarsewhile(!feof(p) && !ferror(p))
o existe una posibilidad muy real de un bucle infinito, probablemente arrojando todo tipo de basura como Se están procesando datos no válidos.Entonces, en resumen, aunque no puedo afirmar con certeza que nunca haya una situación en la que pueda ser semánticamente correcto escribir "
while(!feof(f))
" (aunque debe haber otra verificación dentro del bucle con una interrupción para evitar un bucle infinito en un error de lectura ), es casi seguro que siempre está mal. E incluso si 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 debe 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).fuente
feof(file) || ferror(file)
, por lo que es muy diferente. Pero esta pregunta no pretende ser aplicable a C ++.No, no siempre está mal. Si su condición de bucle es "mientras no hemos tratado de leer el 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 algo más (como "¿puedo leer más").while (!feof(f))
no está mal, solo se usa mal.fuente
f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
o (voy a probar esto)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
while(!eof(f))
feof
no se trata de detectar el final del archivo; se trata de determinar si una lectura fue corta debido a un error o porque la entrada está agotada.feof()
indica si uno 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 próxima operación de entrada fallará (no está seguro de que la anterior fallara, por cierto), pero si es falsa, no está seguro de la siguiente entrada La operación tendrá éxito. Además, las operaciones de entrada pueden fallar por otras razones que no sean el final del archivo (un error de formato para la entrada formateada, una falla de E / S pura - 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 dispositivos interactivos, a veces forzando la entrada del siguiente línea antes de comenzar el manejo del anterior),Entonces, la expresión correcta 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:
fuente
else
no es posible consizeof(line) >= 2
yfgets(line, sizeof(line), file)
pero posible con patológicosize <= 0
yfgets(line, size, file)
. Quizás incluso posible consizeof(line) == 1
.feof(f)
NO PREDICE nada. Establece que una operación ANTERIOR ha llegado al final del archivo. Nada más y nada menos. Y si no hubo una operación previa (solo la 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 seguirfeof(f)
.feof()
No es muy intuitivo. En mi muy humilde opinión, el estadoFILE
de fin de archivo debe establecerse entrue
si cualquier operación de lectura resulta en el final del archivo. En su lugar, debe verificar manualmente si se alcanzó el final del archivo después de cada operación de lectura. Por ejemplo, algo como esto funcionará si lee de un archivo de texto usandofgetc()
:Sería genial si algo como esto funcionara en su lugar:
fuente
printf("%c", fgetc(in));
? Ese es un comportamiento indefinido.fgetc()
vuelveint
, nochar
.while( (c = getchar()) != EOF)
es mucho "algo como esto".while( (c = getchar()) != EOF)
funciona en uno de mi escritorio con GNU C 10.1.0, pero falla en mi Raspberry Pi 4 con GNU C 9.3.0. En mi RPi4, no detecta el final del archivo y simplemente continúa.char c
aint c
obras! ¡¡Gracias!!