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:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
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
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
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:
for (int n; std::cin >> n; ) {
consume(n);
}
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:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Le résultat que nous devons utiliser est à nouveau std::cin
, comme auparavant.
POSIX, write(2)
pour vider un tampon:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; 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 pouvons seulement savoir combien d'octets ont été écrits après l'opération d'écriture.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
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:
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 ici deux résultats. Le premier est iss
, l'objet de flux lui-même, de vérifier que l'extraction formatée a value
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.
feof()
pour contrôler une boucle