J'ai vu des gens essayer de lire des fichiers comme celui-ci dans de nombreux articles récemment:
#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;
}
Quel est le problème avec cette boucle?
feof()
pour contrôler une boucleRéponses:
Je voudrais fournir une perspective abstraite de haut niveau.
Concurrence et simultanéité
Les opérations d'E / S interagissent avec l'environnement. L'environnement ne fait pas partie de votre programme et n'est pas sous votre contrôle. L'environnement existe vraiment "simultanément" avec votre programme. Comme pour tout ce qui est simultané, les questions sur «l'état actuel» n'ont pas de sens: il n'y a pas de concept de «simultanéité» entre les événements simultanés. De nombreuses propriétés de l' Etat ne sont pas simplement existent en même temps.
Permettez-moi de préciser ceci: supposons que vous vouliez demander: «avez-vous plus de données». Vous pouvez le demander à un conteneur simultané ou à votre système d'E / S. Mais la réponse est généralement inutilisable, et donc dénuée de sens. Et si le conteneur dit "oui" - au moment où vous essayez de lire, il peut ne plus avoir de données. De même, si la réponse est «non», au moment où vous essayez de lire, les données peuvent être arrivées. La conclusion est qu'il ya tout simplement estaucune propriété comme «J'ai des données», car vous ne pouvez pas agir de manière significative en réponse à une réponse possible. (La situation est légèrement meilleure avec une entrée en mémoire tampon, où vous pourriez peut-être obtenir un "oui, j'ai des données" qui constitue une sorte de garantie, mais vous devriez toujours être en mesure de faire face au cas contraire. Et avec la sortie, la situation est certainement aussi mauvais que je l'ai décrit: on ne sait jamais si ce disque ou ce tampon réseau est plein.)
Donc , nous concluons qu'il est impossible, et en fait un raisonnable , de demander un système d' E / S si elle sera en mesure d'effectuer une opération d'E / S. La seule façon possible d'interagir avec lui (tout comme avec un conteneur simultané) est de tenter l'opération et de vérifier si elle a réussi ou échoué. À ce moment où vous interagissez avec l'environnement, alors et seulement alors, vous pouvez savoir si l'interaction était réellement possible, et à ce stade, vous devez vous engager à effectuer l'interaction. (C'est un "point de synchronisation", si vous voulez.)
EOF
Nous arrivons maintenant à EOF. EOF est la réponse que vous obtenez d'une tentative d' opération d'E / S. Cela signifie que vous tentiez de lire ou d'écrire quelque chose, mais ce faisant, vous n'avez pas réussi à lire ou à écrire des données, et à la place la fin de l'entrée ou de la sortie a été rencontrée. Cela est vrai pour pratiquement toutes les API d'E / S, qu'il s'agisse de la bibliothèque standard C, des iostreams C ++ ou d'autres bibliothèques. Tant que les opérations d'E / S réussissent, vous ne pouvez tout simplement pas savoir si les opérations futures réussiront. Vous devez toujours d'abord essayer l'opération, puis répondre au succès ou à l'échec.
Exemples
Dans chacun des exemples, notez attentivement que nous tentons d' abord l'opération d'E / S, puis consommons le résultat s'il est valide. Notez en outre que nous devons toujours utiliser le résultat de l'opération d'E / S, bien que le résultat prenne des formes et des formes différentes dans chaque exemple.
C stdio, lu depuis un fichier:
Le résultat que nous devons utiliser est
n
le nombre d'éléments qui ont été lus (qui peut être aussi petit que zéro).C stdio,
scanf
:Le résultat que nous devons utiliser est la valeur de retour de
scanf
, le nombre d'éléments convertis.C ++, extraction au format iostreams:
Le résultat que nous devons utiliser est
std::cin
lui - même, qui peut être évalué dans un contexte booléen et nous indique si le flux est toujours à l'good()
état.C ++, iostreams getline:
Le résultat que nous devons utiliser est à nouveau
std::cin
, comme auparavant.POSIX,
write(2)
pour vider un tampon:Le résultat que nous utilisons ici est
k
le nombre d'octets écrits. Le point ici est que nous pouvons seulement savoir combien d'octets ont été écrits après l'opération d'écriture.POSIX
getline()
Le résultat que nous devons utiliser est
nbytes
le nombre d'octets jusqu'à et y compris la nouvelle ligne (ou EOF si le fichier ne se termine pas par une nouvelle ligne).Notez que la fonction renvoie explicitement
-1
(et non EOF!) Lorsqu'une erreur se produit ou qu'elle atteint EOF.Vous remarquerez peut-être que nous épelons très rarement le mot "EOF". Nous détectons généralement la condition d'erreur d'une autre manière qui nous intéresse plus immédiatement (par exemple, l'échec à effectuer autant d'E / S que nous le souhaitions). Dans chaque exemple, il existe une fonctionnalité d'API qui pourrait nous dire explicitement que l'état EOF a été rencontré, mais ce n'est en fait pas une information extrêmement utile. C'est beaucoup plus un détail que ce dont nous nous soucions souvent. Ce qui importe, c'est de savoir si les E / S ont réussi, plus que comment elles ont échoué.
Un dernier exemple qui interroge réellement l'état EOF: Supposons que vous ayez une chaîne et que vous souhaitiez tester qu'elle représente un entier dans son intégralité, sans bits supplémentaires à la fin, sauf les espaces. En utilisant les iostreams C ++, cela se passe comme suit:
Nous utilisons ici deux résultats. Le premier est
iss
, l'objet de flux lui-même, de vérifier que l'extraction formatée avalue
réussi. Mais ensuite, après avoir également consommé des espaces, nous effectuons une autre opération d'E / S /iss.get()
et nous nous attendons à ce qu'elle échoue en tant qu'EOF, ce qui est le cas si la chaîne entière a déjà été consommée par l'extraction formatée.Dans la bibliothèque standard C, vous pouvez obtenir quelque chose de similaire avec les
strto*l
fonctions en vérifiant que le pointeur de fin a atteint la fin de la chaîne d'entrée.La réponse
while(!feof)
est erroné car il teste quelque chose qui n'est pas pertinent et ne parvient pas à tester quelque chose que vous devez savoir. Le résultat est que vous exécutez par erreur du code qui suppose qu'il accède à des données qui ont été lues avec succès, alors qu'en fait cela ne s'est jamais produit.la source
feof()
ne "demande pas au système d'E / S s'il a plus de données".feof()
, selon la page de manuel (Linux) : "teste l'indicateur de fin de fichier pour le flux pointé par stream, retournant différent de zéro s'il est défini." (également, un appel explicite àclearerr()
est le seul moyen de réinitialiser cet indicateur); À cet égard, la réponse de William Pursell est bien meilleure.C'est faux parce que (en l'absence d'erreur de lecture), il entre dans la boucle une fois de plus que ce que l'auteur attend. S'il y a une erreur de lecture, la boucle ne se termine jamais.
Considérez le code suivant:
Ce programme imprimera systématiquement un plus grand que le nombre de caractères dans le flux d'entrée (en supposant qu'aucune erreur de lecture). Considérez le cas où le flux d'entrée est vide:
Dans ce cas,
feof()
est appelé avant la lecture des données, il renvoie donc false. La boucle est entrée,fgetc()
est appelée (et retourneEOF
) et le nombre est incrémenté. Puisfeof()
est appelé et renvoie true, provoquant l'abandon de la boucle.Cela se produit dans tous ces cas.
feof()
ne renvoie vrai qu'après une lecture sur le flux rencontre la fin du fichier. Le but defeof()
n'est PAS de vérifier si la prochaine lecture atteindra la fin du fichier. Le but defeof()
est de distinguer entre une erreur de lecture et avoir atteint la fin du fichier. Sifread()
renvoie 0, vous devez utiliserfeof
/ferror
pour décider si une erreur a été rencontrée ou si toutes les données ont été consommées. De même sifgetc
retourneEOF
.feof()
n'est utile que lorsque fread a renvoyé zéro oufgetc
est revenuEOF
. Avant cela,feof()
renvoie toujours 0.Il est toujours nécessaire de vérifier la valeur de retour d'une lecture (soit un
fread()
, soit unfscanf()
, ou unfgetc()
) avant d'appelerfeof()
.Pire encore, considérons le cas où une erreur de lecture se produit. Dans ce cas,
fgetc()
renvoieEOF
,feof()
renvoie faux et la boucle ne se termine jamais. Dans tous les cas où ilwhile(!feof(p))
est utilisé, il doit y avoir au moins une vérification à l'intérieur de la boucleferror()
, ou tout au moins la condition while doit être remplacée parwhile(!feof(p) && !ferror(p))
ou il existe une possibilité très réelle de boucle infinie, déversant probablement toutes sortes de déchets comme des données non valides sont en cours de traitement.Donc, en résumé, bien que je ne puisse pas affirmer avec certitude qu'il n'y a jamais de situation dans laquelle il peut être sémantiquement correct d'écrire "
while(!feof(f))
" (bien qu'il doit y avoir une autre vérification à l'intérieur de la boucle avec une pause pour éviter une boucle infinie sur une erreur de lecture ), il est vrai que c'est presque certainement toujours faux. Et même si un cas se présentait où il serait correct, il est si idiomatiquement erroné que ce ne serait pas la bonne façon d'écrire le code. Quiconque voit ce code devrait immédiatement hésiter et dire "c'est un bug". Et éventuellement gifler l'auteur (sauf si l'auteur est votre patron, auquel cas la discrétion est conseillée.)la source
feof(file) || ferror(file)
, donc c'est très différent. Mais cette question n'est pas destinée à s'appliquer au C ++.Non, ce n'est pas toujours faux. Si votre condition de boucle est "alors que nous n'avons pas essayé de lire la fin du fichier", vous utilisez
while (!feof(f))
. Ce n'est cependant pas une condition de boucle courante - généralement, vous voulez tester autre chose (comme "puis-je en savoir plus").while (!feof(f))
ce n'est pas faux, c'est juste mal utilisé .la source
f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
ou (va tester cela)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
while(!eof(f))
feof
ne concerne pas la détection de la fin du fichier; il s'agit de déterminer si une lecture a été courte à cause d'une erreur ou parce que l'entrée est épuisée.feof()
indique si l'on a essayé de lire après la fin du fichier. Cela signifie qu'il a peu d'effet prédictif: si c'est vrai, vous êtes sûr que la prochaine opération d'entrée échouera (vous n'êtes pas sûr que la précédente a échoué BTW), mais si elle est fausse, vous n'êtes pas sûr de la prochaine entrée l'opération réussira. De plus, les opérations d'entrée peuvent échouer pour d'autres raisons que la fin du fichier (une erreur de format pour une entrée formatée, une panne pure d'E / S - panne de disque, délai d'expiration du réseau - pour tous les types d'entrée), donc même si vous pouviez être prédictif sur la fin du fichier (et quiconque a essayé d'implémenter Ada one, qui est prédictif, vous dira qu'il peut être complexe si vous avez besoin de sauter des espaces, et qu'il a des effets indésirables sur les appareils interactifs - forçant parfois l'entrée du suivant avant de commencer le traitement de la précédente),Ainsi, l'idiome correct en C est de boucler avec le succès de l'opération d'E / S comme condition de boucle, puis de tester la cause de l'échec. Par exemple:
la source
else
pas possible avecsizeof(line) >= 2
etfgets(line, sizeof(line), file)
mais possible avec pathologiquesize <= 0
etfgets(line, size, file)
. Peut-être même possible avecsizeof(line) == 1
.feof(f)
cela ne prédit rien. Il indique qu'une opération PRÉCÉDENTE a atteint la fin du fichier. Ni plus ni moins. Et s'il n'y avait pas eu d'opération précédente (juste l'ouvrir), il ne signale pas la fin du fichier même si le fichier était vide au départ. Donc, à part l'explication de la concurrence dans une autre réponse ci-dessus, je ne pense pas qu'il y ait de raison de ne pas continuerfeof(f)
.feof()
n'est pas très intuitif. À mon humble avis, l'étatFILE
de fin de fichier doit être défini surtrue
si une opération de lecture aboutit à la fin du fichier. Au lieu de cela, vous devez vérifier manuellement si la fin du fichier a été atteinte après chaque opération de lecture. Par exemple, quelque chose comme ça fonctionnera si vous lisez à partir d'un fichier texte en utilisantfgetc()
:Ce serait génial si quelque chose comme ça fonctionnait à la place:
la source
printf("%c", fgetc(in));
? C'est un comportement indéfini.fgetc()
renvoieint
, nonchar
.while( (c = getchar()) != EOF)
est vraiment "quelque chose comme ça".while( (c = getchar()) != EOF)
fonctionne sur l'un de mes postes de travail exécutant GNU C 10.1.0, mais échoue sur mon Raspberry Pi 4 exécutant GNU C 9.3.0. Sur mon RPi4, il ne détecte pas la fin du fichier et continue simplement.char c
auxint c
travaux! Merci!!