J’ai commencé à voir `` dans beaucoup de messages ces derniers temps, et je n’ai pas trouvé un bon lien à une référence d’expliquer pourquoi c’est faux. Donc je pensais que je prendrais un coup de poignard à expliquer ici.
Réponses
Trop de publicités?J'aimerais fournir un résumé, une perspective de haut niveau.
La simultanéité et la simultanéité
Opérations d'e/S interagir avec l'environnement. L'environnement ne fait pas partie de votre programme, et non pas sous votre contrôle. L'environnement existe vraiment "simultanément" avec votre programme. Comme avec toutes les choses simultanées, des questions sur "l'état actuel" ne faites pas de sens: Il n'y a pas de notion de "simultanéité" à travers les événements simultanés. De nombreuses propriétés de l'etat n'ont tout simplement pas exister en même temps.
Permettez-moi de rendre cela plus précis: Supposons que vous souhaitez demander, "vous avez plus de données". Vous pourriez vous demander ce de un conteneur simultané, ou de votre système d'e/S. Mais la réponse est généralement unactionable, et donc vide de sens. De sorte que si le conteneur dit "oui" – au moment où vous essayez de lire, il ne peut plus avoir de données. De même, si la réponse est "non", au moment où vous essayez de lecture, les données peuvent être arrivés. La conclusion est que, n' est pas la propriété comme "j'ai des données", puisque vous ne pouvez pas agir de manière significative dans la réponse à toute les réponses possibles. (La situation est légèrement meilleure avec tampon d'entrée, où vous pourriez éventuellement obtenir un "oui, j'ai des données" qui constitue une sorte de garantie, mais vous auriez encore être en mesure de traiter le cas contraire. Et avec la sortie de la situation est certainement tout aussi mauvais que j'ai décrit: vous ne savez jamais si ce disque ou réseau de la mémoire tampon est pleine.)
Donc, nous concluons qu'il est impossible, et, en fait, de l'onuraisonnable, de demander à un système d'e/S s'il sera capable d'effectuer une opération d'e/S. La seule façon de communiquer avec elle (tout comme avec un conteneur simultané) est de tenter l'opération et vérifier si elle a réussi ou échoué. Au moment où vous interagissez avec l'environnement, alors, et seulement alors pouvez-vous savoir si l'interaction est en fait possible, et à ce point que vous devez vous engager à l'exécution de l'interaction. (C'est une "synchronisation", si vous voulez.)
EOF
Nous en arrivons maintenant à l'EOF. EOF est la réponse que vous obtenez à partir d'une tentative d' opération d'e/S. Cela signifie que vous étiez en train de lire ou d'écrire quelque chose, mais en faisant de sorte que vous avez omis de lire ou d'écrire des données, et au lieu de la fin de l'entrée ou de la sortie a été rencontrée. Cela est vrai pour pratiquement tous les I/O Api, que ce soit la bibliothèque standard C, C++ iostreams, ou d'autres bibliothèques. Aussi longtemps que les opérations d'e/S à réussir, vous simplement ne peut pas savoir si d'autres, les opérations à venir va réussir. Vous devez toujours essayer d'abord le fonctionnement et répondre ensuite à la réussite ou à l'échec.
Exemples
Dans chacun des exemples, bien noter que nous première tentative de l'opération d'e/S et ensuite consommer le résultat si elle est valide. A noter de plus, que nous avons toujours devez utiliser le résultat de l'opération d'e/S, si le résultat prend des formes différentes dans chaque exemple.
-
C stdio, lire à partir d'un fichier:
for (;;) { size_t n = fread(buf, 1, bufsize, infile); consume(buf, n); if (n < bufsize) { break; } }
Le résultat, nous devons utiliser est -
n
, le nombre d'éléments qui ont été lues. -
C stdio,
scanf
:for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) { consume(a, b, c); }
Le résultat, nous devons utiliser est la valeur de retour de l'
scanf
, le nombre de convertis en éléments. -
C++, iostreams formaté extraction:
for (int n; std::cin >> n; ) { consume(n); }
Le résultat, nous devons utiliser est -
std::cin
lui-même, qui peut être évaluée dans un contexte booléen et nous indique si le flux est toujours dans l'good()
de l'état. -
C++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) { consume(line); }
Le résultat, nous devons utiliser est de nouveau en
std::cin
, tout comme avant. -
Posix,
write(2)
pour le rinçage d'un tampon:char const * p = buf; ssize_t n = bufsize; for (ssize_t k = bufsize; k = write(fd, p, n); p += k, n -= k) {} if (n != 0) { /* error, failed to write complete buffer */ }
Le résultat que nous utilisons ici est
k
, le nombre d'octets écrits. Le point ici est que nous ne pouvons savoir combien d'octets ont été écrits après l'opération d'écriture.
Vous pouvez remarquer que nous avons très rarement épeler le mot "expressions du FOLKLORE". Nous avons l'habitude de détecter la condition de l'erreur d'une autre façon qui est plus immédiatement intéressant pour nous (par exemple, l'échec à effectuer plus d'I/O comme nous l'avions souhaité). Dans chaque exemple, il y a certaines API fonctionnalité qui pourrait nous dire explicitement que les expressions du FOLKLORE de l'état a été rencontrée, mais ce est en fait pas très utile morceau de l'information. Il est beaucoup plus d'un détail que nous avons souvent des soins. Ce qui importe est de savoir si les I/O a réussi, plus-de sorte que la façon dont il a échoué.
-
Un dernier exemple qui fait des requêtes de l'EOF état: Supposons que vous avez une chaîne et que vous souhaitez tester qu'il représente un nombre entier dans son intégralité, sans aucun supplément de bits à la fin, sauf les espaces. À l'aide de C++ iostreams, il va comme ceci:
std::string input = " 123 "; // example std::istringstream iss(input); int value; if (iss >> value >> std::ws && iss.get() == EOF) { consume(value); } else { // error, "input" is not parsable as an integer }
Nous utilisons deux résultats ici. La première est
iss
, le flux de l'objet lui-même, pour vérifier que la mise en forme d'extraction à l'value
réussi. Mais alors, après avoir également consommer de l'espace, nous effectuons un autre I/O/ opération,iss.get()
, et s'attendre à l'échec puisque les expressions du FOLKLORE, ce qui est le cas si l'ensemble de la chaîne de caractères a déjà été consommée par la mise en forme de l'extraction.Dans la bibliothèque C standard, vous pouvez obtenir quelque chose de similaire avec l'
strto*l
fonctions en vérifiant que la fin pointeur a atteint la fin de la chaîne d'entrée.
La réponse
while(!eof)
est faux, car il des tests pour quelque chose qui n'est pas pertinent et ne parvient pas à tester pour quelque chose que vous devez savoir. Le résultat est que vous avez tort, l'exécution de code qui suppose que l'accès aux données qui a été lu correctement, alors qu'en fait, ce n'est jamais arrivé.
C'est faux parce que (en l'absence d'une erreur de lecture), il entre dans la boucle une fois de plus que l'auteur attend. Si il y a une erreur de lecture, la boucle se termine jamais.
Considérons le code suivant:
/* 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! */
(void) 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;
}
Ce programme sera toujours l'impression de plus grand que le nombre de caractères dans le flux d'entrée (en supposant qu'aucun des erreurs de lecture). Considérons le cas où le flux d'entrée est vide:
$ ./a.out < /dev/null
Number of characters read: 1
Dans ce cas, feof()
est appelé avant que les données ont été lues, elle retourne false. La boucle est entré, fgetc()
est appelé (et les retours EOF
), et le compteur est incrémenté. Ensuite, feof()
est appelée et renvoie vrai, entraînant la boucle pour l'abandonner.
Ce qui se passe dans tous ces cas. feof()
ne retourne pas vrai jusqu'à ce que , après une lecture sur le flux des rencontres de la fin du fichier. Le but de l' feof()
n'est PAS le vérifier si la prochaine lecture sera d'atteindre la fin du fichier. Le but de l' feof()
est la distinction entre une erreur de lecture et d'avoir atteint la fin du fichier. Si fread()
retourne 0, vous devez utiliser feof
/ferror
d'en décider. De même si fgetc
retours EOF
. feof()
n'est utile qu' après fread a repris de zéro ou fgetc
a retourné EOF
. Avant que cela arrive, feof()
retourne toujours 0.
Il est toujours nécessaire de vérifier la valeur de retour de lecture ( fread()
, ou un fscanf()
, ou un fgetc()
) avant d'appeler feof()
.
Pire encore, prenons le cas d'une erreur de lecture se produit. Dans ce cas, fgetc()
retours EOF
, feof()
retourne false, et la boucle se termine jamais. Dans tous les cas où l' while(!feof(p))
est utilisé, il doit y avoir au moins une case à l'intérieur de la boucle pour ferror()
, ou à tout le moins, la condition du while devrait être remplacé par while(!feof(p) && !ferror(p))
ou il y a une possibilité très réelle d'une boucle infinie, probablement crachant toutes sortes de déchets comme des données non valides sont en cours de traitement.
Donc, en résumé, bien que je ne peut pas affirmer avec certitude qu'il n'y est jamais une situation dans laquelle il peut être sémantiquement correct d'écrire "while(!feof(f))
" (bien qu'il doit être une autre vérification à l'intérieur de la boucle, avec une pause pour éviter une boucle infinie sur une erreur de lecture), c'est le cas, elle est presque certainement toujours tort. Et même si une affaire n'est jamais né, où il serait correct, il est donc idiomatique de mal qu'il ne serait pas la bonne façon d'écrire le code. Quelqu'un de voir que le code devrait immédiatement hésitez pas et de dire, "que c'est un bug". Et éventuellement claque de l'auteur (à moins que l'auteur est à votre patron auquel cas, la discrétion est conseillé.)
Non, ce n’est pas toujours mauvais. Si votre condition de boucle est « alors que nous n’avons pas tenté de lire au-delà de fin de fichier », puis vous utilisez . Ce n’est toutefois pas une condition de boucle commune - habituellement, vous voulez tester pour autre chose (comme « peux j’ai lire la suite »).
n’est pas mal, c’est juste utilisé mal.
Pour ceux qui recherchent une solution rapide, ce post contient quelques-uns des moyens sûrs pour lire un fichier en ISO C, bien qu'ils ne sont évidemment pas les seules façons. while (!feof(fp))
n'est pas toujours mauvais, mais quand vous apprenez à travailler avec des fichiers de plus en plus fortement, il y a des choses subtiles, vous devez être conscient de. Bien sûr, vous devriez toujours vérifier vos valeurs de retour des fonctions qui les fournissent. ;-)
Exemple 1: à l'aide d' fgetc
correctement
/*
* ch MUST be an int. Otherwise you can't distinguish between
* (unsigned char)255 and EOF, and this won't work.
*/
int ch;
while ((ch = fgetc(fp)) != EOF) {
// do something with ch
}
if (ferror(fp)) {
// handle the error, usually exit or return
} else {
// continue execution
}
fgetc
retours EOF
lorsqu'une erreur se produit ou lorsque la fin du fichier est atteinte. Vous faites une distinction entre les deux boucle cas de résiliation à l'aide de l'une des ferror
ou feof
après la boucle.
Exemple 2: à l'aide d' fgets
correctement
while (fgets(buffer, buffer_size, fp)) {
// do something with buffer
}
if (ferror(fp)) {
// handle the error, usually exit or return
} else {
// continue execution
}
fgets
retours NULL
sur l'erreur, et le contenu de la mémoire tampon sont imprévisibles. Dans le cas de la fin du fichier est atteinte si les caractères ont été lus, il retourne buffer
. Si aucun des caractères ont été lus, il retourne NULL
. Encore une fois, utilisez ferror
ou feof
de distinguer entre les deux cas, pour qui fgets
retours NULL
.
Exemple 3: utilisation d' fread
correctement
size_t nret;
size_t nmemb = 256;
while (nmemb == (nret = fread(buffer, sizeof *buffer, nmemb, fp))) {
// do something with buffer
}
if (nret) {
// do the same thing with buffer that was in the loop
}
if (ferror(fp)) {
// handle the error, probably exit/return
} else {
// continue execution
}
Celui-ci est un peu différente en raison de la fread
travaux. La fonction lit les données binaires à partir d'un fichier et le stocke dans une mémoire tampon, la lecture, nmemb
des éléments de la taille spécifiée (sizeof *buffer
dans l'exemple) ou autant qu'il le peut.
fread
retourne le nombre d'éléments de la taille spécifiée correctement lues. Si elle est inférieure à nmemb
, une erreur s'est produite ou la fin du fichier a été atteinte. Si la fin du fichier a été atteinte ou une erreur s'est produite, il peut y avoir de lecture de données, signification, nret
est supérieur à 0. En conséquence, ce qui a été fait avec les données lues à l'intérieur de la boucle doit être fait une fois de plus pour terminer le traitement du fichier, autant que possible, si vous le souhaitez (si vous choisissez de traiter les données à lire lorsqu'une erreur se produit est votre option.)
Après cela, l'habituel ferror
/feof
vérification arrive.
Exemple 4: utilisation de l' fscanf
correctement
int nfields = 3;
size_t lineno = 0;
// Read a CSV file containing a list of 3-D vectors as floats.
while (!feof(fp)) {
++lineno;
while (nfields == (nret = fscanf(fp, "%f,%f,%f", &vec[0], &vec[1], &vec[2]))) {
// do something with the vector read
++lineno;
}
if (ferror(fp)) {
// handle the error, usually exit/return
} else if (nret != EOF) {
fprintf(stderr, "warning: ignoring malformed line %zu\n", lineno);
fscanf(fp, "%*[^\n]");
}
}
// continue execution
fscanf
s'comportement est sans doute un mélange de tant de fgetc
et fread
. Il retourne EOF
si une erreur ou à la fin du fichier est détecté, et pas d'entrée conversions ont été appariés encore. Sinon, elle renvoie le nombre de attribué éléments d'entrée. Comme fread
, ce sont peut-être moins que prévu, ce qui indique incorrectement formatée de données ou à une erreur ou à la fin du fichier.
Si la valeur n'a pas de signal une erreur, et la valeur retournée par fscanf
n'est pas EOF
, ce qui signifie que ni ferror
ni feof
doit retourner une valeur autre que 0, il y a une mauvaise mise en forme des données (il n'aurait pas sorti de la boucle interne contraire.) Si elle n'est pas vraie, à la fin du fichier a été détecté, et que l'extérieur de la condition de boucle est déclenchée, en sortant de la boucle, ce qui signale la fin du fichier a été atteinte.
Notez que j'ai utilisé un scanset dans le dernier bit d'ignorer une ligne non valide entièrement, qui ne fonctionne qu'avec un conforme C99 bibliothèque. On pourrait faire la même chose avec une boucle à l'aide de fgetc
ou avec une boucle à l'aide de fgets
si vous n'avez pas une bibliothèque C disponible (n'oubliez pas de vérifier à la fin du fichier!)
Contrairement aux autres exemples, j'ai appliqué un petit cas concret de l' fscanf
exemple parce formaté d'entrée des demandes. Si elle n'est pas concrète, comment pouvez-vous savoir à certains qu'il fonctionne?
La fermeture de notes
Il est mon espoir que cette réponse aide à quelqu'un parce que c'était la seule réponse que j'ai vu qui explique comment le faire avec au moins un exemple de code plutôt que les paragraphes qui ne sont pas nécessairement expliquer tous les "pièges". L'idée était de fournir un guidage de modèle pour illustrer comment lire les données à partir d'un fichier de façon appropriée et en toute sécurité, même si évidemment il n'est pas nécessairement valable pour tous les cas d'utilisation.
while (!feof(fp))
n'est pas toujours mauvais, et avec la façon dont les choses fonctionnent avec fscanf
en particulier, le code de l'Exemple 4 effectivement bénéficié de son utilisation en raison de l'ignorance des erreurs a été désiré, mais j'espère qu'aucun de ces vecteurs qui ont été ignorés étaient terriblement critique, tels que les points de un cube pour un 3-D de la carte... Ce qui serait un cube avec seulement 4 points par exemple? ;-)
Évidemment, cette ignorance n'est pas toujours souhaitée, en particulier lors de la lecture du texte ou des données binaires à partir, par exemple, un fichier de configuration, qui est la principale raison les trois premiers exemples ne permettent pas de tenir compte des données malformées; j'aurais utilisé la même technique que dans l'Exemple 2 comme je l'ai fait dans l'Exemple 4 à ignorer un mal formé ligne dans un fichier INI de Windows, mais je ne peux pas le faire avec le mal imbriquées les éléments XML par exemple. Bien sûr il y a des exceptions, mais c'est une question de détail de l'implémentation et de la désirée et les fonctionnalités disponibles.
Espérons que cela aide quelqu'un! :-)
feof() indique si l'on a essayé de lire au-delà de la fin de fichier. Cela signifie qu'il est peu prédictif de l'effet: si c'est vrai, vous êtes sûr que la prochaine opération d'entrée sera un échec (vous n'êtes pas sûr de la précédente a échoué d'ailleurs), mais si c'est faux, vous n'êtes pas sûr de la prochaine opération d'entrée sera de réussir. De plus, les opérations d'entrée peut échouer pour des raisons autres que la fin du fichier (une erreur de format pour formaté entrée, un pur IO échec-échec de disque, délai d'attente réseau -- pour la saisie de tous les types), de sorte que même si vous pourriez être un facteur prédictif sur la fin du fichier (et quiconque a essayé de mettre en œuvre Ada, qui est un facteur prédictif, vous dira qu'il peut complexe si vous avez besoin de sauter des espaces, et qu'il a des effets indésirables sur les dispositifs interactifs, parfois en forçant l'entrée de la ligne suivante avant de commencer la manipulation de la précédente), vous devez être en mesure de gérer un échec.
Donc la bonne idiome C est de faire une boucle avec le IO opération de succès en tant que condition de boucle, puis de tester la cause de l'échec. Par exemple:
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) */
}