Warum “while (!feof(Datei))" immer falsch???
Ich habe gesehen, Menschen, die versuchen, Dateien zu Lesen, wie dies in einer Menge posts in letzter Zeit.
Code
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char * path = argc > 1 ? argv[1] : "input.txt";
FILE * fp = fopen(path, "r");
if( fp == NULL ) {
perror(path);
return EXIT_FAILURE;
}
while( !feof(fp) ) { /* THIS IS WRONG */
/* Read and process data from file… */
}
if( fclose(fp) == 0 ) {
return EXIT_SUCCESS;
} else {
perror(path);
return EXIT_FAILURE;
}
}
Was ist falsch mit diesem while( !feof(fp))
Schleife?
Aufteilen Ihre Frage in der 2. Ein Teil, wie die Frage der richtigen und der andere Teil als Antwort; dann setzen, dass der 2. Teil da unten ... in den Antwort-Abschnitt SO 🙂
Warum ist es schlecht zu verwenden
Warum ist iostream::eof innerhalb einer Schleife die Bedingung als falsch?
Kurze Antwort: Es versucht, eine status-reporting-Funktion, um vorherzusagen, ob eine zukünftige operation erfolgreich sein wird.
Wie ist das, ich hatte nie ein problem mit?
Warum ist es schlecht zu verwenden
feof()
zur Steuerung einer SchleifeWarum ist iostream::eof innerhalb einer Schleife die Bedingung als falsch?
Kurze Antwort: Es versucht, eine status-reporting-Funktion, um vorherzusagen, ob eine zukünftige operation erfolgreich sein wird.
Wie ist das, ich hatte nie ein problem mit?
InformationsquelleAutor William Pursell | 2011-03-25
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich würde gerne eine abstrakte, high-level-Perspektive.
Parallelität und Gleichzeitigkeit
I/O-Operationen mit der Umwelt interagieren. Die Umgebung ist nicht Teil des Programms, und nicht unter Ihrer Kontrolle. Die Umwelt existiert wirklich "gleichzeitig" mit Ihrem Programm. Wie mit allen Dingen gleichzeitig, Fragen über den "aktuellen Stand" machen keinen Sinn: Es gibt kein Konzept von "Gleichzeitigkeit" über gleichzeitige Ereignisse. Viele Eigenschaften, die der Staat einfach nicht existieren gleichzeitig.
Lassen Sie mich dies genauer: Angenommen, Sie wollen, zu Fragen, "haben Sie noch mehr Daten". Man könnte diese Frage einer gleichzeitigen container, oder von Ihrem I/O-system. Aber die Antwort ist in der Regel unactionable, und damit bedeutungslos. Was also, wenn der container sagt "ja" – durch die Zeit, die Sie versuchen zu Lesen, es können keine Daten mehr habe. Ebenso, wenn die Antwort "Nein" ist, durch die Zeit, die Sie versuchen zu Lesen, können die Daten angekommen sind. Die Schlussfolgerung ist, dass es einfach ist keine Eigenschaft wie "ich habe die Daten", da Sie nicht handeln kann sinnvoll in Antwort auf eine mögliche Antwort. (Die situation ist etwas besser mit Gepufferter Eingang, wo Sie könnten möglicherweise Holen Sie sich ein "ja, ich habe Daten" , stellt eine Art Garantie, aber Sie würde immer noch in der Lage, sich mit den entgegengesetzten Fall. Und mit dem Ausgang der situation ist sicherlich genauso schlecht wie ich es beschrieben: man weiß nie, wenn die Festplatte oder das Netzwerk-Puffer voll ist.)
So können wir daraus folgern, dass es unmöglich ist, und in der Tat un - angemessenen, bitten die I/O-system, ob es werden in der Lage zum ausführen einer I/O-operation. Die einzig mögliche Art, wie wir mit ihm interagieren können (genau wie mit einer gleichzeitigen container) Versuch den Betrieb und überprüfen, ob es erfolgreich war oder nicht. In diesem moment, wo Sie interagieren mit der Umgebung, dann, und nur dann kann man wissen, ob die Interaktion tatsächlich möglich war, und an diesem Punkt müssen Sie einen commit für die Durchführung der Interaktion. (Dies ist eine "synchronisation point", wenn man so will.)
EOF
Nun kommen wir zum EOF. EOF ist die Antwort man von einer versucht I/O-operation. Es bedeutet, dass Sie versuchen zu Lesen oder etwas schreiben, aber wenn Sie dies tun, Sie konnte nicht Lesen oder schreiben Sie alle Daten, und stattdessen das Ende des Eingabe-und Ausgabe festgestellt wurde. Dies gilt im wesentlichen für alle I/O-APIs, ob es der C-standard-Bibliothek C++ iostreams, oder anderen Bibliotheken. Solange die I/O-Operationen erfolgreich sind, Sie einfach nicht wissen, ob weitere, zukünftige Operationen gelingt. Sie muss immer zuerst versuchen, den Betrieb und die antwortet dann für Erfolg oder Misserfolg.
Beispiele
In jedem der Beispiele, beachten Sie sorgfältig, dass wir ersten versuchen, die I/O-operation und dann verbrauchen das Ergebnis, wenn es gültig ist. Beachten Sie weiterhin, dass wir immer muss das Ergebnis der I/O-operation, wenn das Ergebnis nimmt verschiedene Formen und Formen in jedem Beispiel.
C stdio, die aus einer Datei gelesen:
Dem Ergebnis, das wir verwenden müssen, ist
n
die Anzahl der Elemente, die gelesen wurden, (die möglicherweise als kleiner als null).C stdio,
scanf
:Dem Ergebnis, das wir verwenden müssen, ist der return-Wert von
scanf
die Anzahl der Elemente umgewandelt.C++ iostreams formatiert Extraktion:
Dem Ergebnis, das wir verwenden müssen, ist
std::cin
selbst, die ausgewertet werden können, die in einem booleschen Kontext und sagt uns, ob der stream ist noch in dergood()
Zustand.C++ iostreams getline:
Dem Ergebnis, das wir verwenden müssen, ist wieder
std::cin
wie vor.POSIX,
write(2)
flush-Puffer:Dem Ergebnis, das wir hier benutzen, ist
k
, die Anzahl der geschriebenen bytes. Der Punkt hier ist, dass wir nur wissen, wie viele bytes geschrieben wurden nach der write-operation.POSIX
getline()
Dem Ergebnis, das wir verwenden müssen, ist
nbytes
, die Anzahl der bytes bis einschließlich der newline (oder EOF, wenn die Datei nicht am Ende mit einem Zeilenumbruch).Beachten Sie, dass die Funktion explizit zurück
-1
(und nicht EOF!) wenn ein Fehler Auftritt oder EOF erreicht.Können Sie feststellen, dass wir sehr selten buchstabieren das Wort "EOF". Wir erkennen in der Regel den Fehler in irgendeiner anderen Weise, die mehr sofort interessant für uns (z.B. Nichterfüllung, so viel I/O, wie wir es gewünscht hatte). In jedem Beispiel gibt es einige API-Funktion, die konnte uns sagen ausdrücklich, dass die EOF-Zustand aufgetreten, dieser ist aber in der Tat keine sehr hilfreiche information. Es ist viel mehr ein detail als wir oft kümmern. Was zählt, ist, ob die I/O gelang es, so mehr, als, wie es scheiterte.
Einem letzten Beispiel, dass eigentlich fragt den EOF-Status: Angenommen Sie haben einen string und möchten, um zu testen, ob es für eine ganze Zahl in seiner Gesamtheit, mit keine zusätzlichen bits am Ende, außer whitespace-Zeichen. Mit C++ iostreams, es geht so:
Wir verwenden zwei Ergebnisse hier. Die erste ist
iss
, das stream-Objekt selbst, um zu überprüfen, dass die formatierten Extraktion zuvalue
gelungen. Aber dann, nachdem auch konsumieren Leerzeichen, führen wir ein weiteres I/O/Betriebiss.get()
, und erwarten, dass es zu scheitern, wie EOF, das ist der Fall, wenn der gesamte string hat, die bereits verbraucht worden, die von der formatierten Extraktion.In der C-standard-Bibliothek, die Sie erreichen können, etwas ähnliches mit dem
strto*l
Funktionen, indem Sie die Ende-Zeiger erreicht das Ende der Eingabezeichenfolge.Die Antwort
while(!eof)
ist falsch, weil es tests für etwas, was irrelevant ist und nicht testen, für etwas, das Sie brauchen, um wissen. Das Ergebnis ist, dass Sie fälschlicherweise code ausführen, wird davon ausgegangen, dass es beim Zugriff auf die Daten, die erfolgreich gelesen, wenn in der Tat dies nie passiert ist.Aber der ANSI-C nicht.
Es ist schlecht für alle die Gründe, die ich erwähnen: Sie können nicht in die Zukunft schauen. Sie können nicht sagen, was in der Zukunft geschehen wird.
Ja, das wäre angemessen, obwohl in der Regel kombinieren Sie dieses check-in-Vorgang (da die meisten iostreams Operationen wieder das stream-Objekt, das sich eine boolean-Konvertierung), und dass die Weise, die Sie machen es offensichtlich, dass Sie nicht ignorieren, die Rückkehr Wert.
Die
std::cin
undscanf
Beispiele gerne behandeln schlechten input (etwas Wert ist, einen Fehler auslösen) als EOF (was nur bedeutet verlassen der Schleife).InformationsquelleAutor Kerrek SB
Es ist falsch, weil (in Ermangelung eines read-error) es in die Schleife ein weiteres mal, als der Autor es erwartet. Wenn es ein Lesefehler ist, wird die Schleife nie beendet.
Betrachten Sie den folgenden code:
Dieses Programm wird konsequent Druck um eins größer als die Anzahl der Zeichen im input-stream (vorausgesetzt, es ist kein Lesefehler). Betrachten wir den Fall, wo der input-stream ist leer:
In diesem Fall
feof()
aufgerufen wird, bevor alle Daten gelesen wurden, so gibt Sie false zurück. Die Schleife eingegeben wird, wirdfgetc()
genannt wird (und zurückEOF
), und der Zähler inkrementiert. Dannfeof()
aufgerufen und gibt true zurück, wodurch die Schleife abgebrochen wird.Dies geschieht in allen solchen Fällen.
feof()
nicht true zurückgeben, bis nach ein Lesen auf dem stream trifft auf das Ende der Datei. Der Zweck derfeof()
ist NICHT zu prüfen, wenn der nächste gelesen wird, erreicht das Ende der Datei. Der Zweck derfeof()
ist die Unterscheidung zwischen einem lese-Fehler und erreichte das Ende der Datei. Wennfread()
0 zurückgibt, müssen Siefeof
/ferror
zu entscheiden. Ebenso, wennfgetc
zurückEOF
.feof()
ist nur sinnvoll, nach fread zurückgekehrt ist null oderfgetc
zurückgekehrtEOF
. Bevor das passiert,feof()
wird immer 0 zurück.Ist es immer notwendig, überprüfen Sie den Rückgabewert von read (entweder ein
fread()
oder einefscanf()
oder einefgetc()
) vor dem Aufruffeof()
.Sogar noch schlimmer, betrachten wir den Fall, wo ein Lesefehler Auftritt. In diesem Fall
fgetc()
zurückEOF
,feof()
gibt false zurück, und die Schleife nie beendet. In allen Fällen, in denenwhile(!feof(p))
verwendet wird, muss es mindestens ein check innerhalb der Schleife fürferror()
oder zumindest die while-Bedingung sollte ersetzt werden, mitwhile(!feof(p) && !ferror(p))
oder es ist eine sehr Reale Möglichkeit einer Endlosschleife, wahrscheinlich spuckt allen möglichen Müll als ungültige Daten verarbeitet werden.So, in Zusammenfassung, obwohl ich nicht mit Gewissheit sagen, dass es nie eine situation, in der es möglicherweise semantisch korrekt zu schreiben "
while(!feof(f))
" (obwohl es muss ein weiterer check innerhalb der Schleife mit einem break zur Vermeidung einer Endlosschleife auf einen lese-Fehler), ist es der Fall, dass es fast sicher immer falsch. Und selbst wenn ein Fall überhaupt entstand, wo es richtig wäre, es ist so idiomatically falsch, dass es nicht der richtige Weg sein, den code zu schreiben. Jemand zu sehen, der code sollte sofort ein zögern und sagen, "das ist ein Fehler". Und vielleicht Schlag der Autor (es sei denn, der Autor ist Ihr Chef, in dem Fall Diskretion wird empfohlen.)Sicher, es ist falsch-aber abgesehen davon, dass es nicht "gratingly hässlich".
Sollten Sie ein Beispiel für die korrekte code, wie ich mir vorstellen, viele Menschen kommen hierher auf der Suche nach eine schnelle Lösung.
Ich bin kein C++ Experte, aber ich glaube-Datei.eof() gibt die effektiv das gleiche Ergebnis wie
feof(file) || ferror(file)
, so ist es sehr unterschiedlich. Aber diese Frage ist nicht beabsichtigt, die Anwendung auf C++.das ist nicht richtig-entweder, weil Sie immer noch versuchen zu verarbeiten, eine zu Lesen, ist fehlgeschlagen.
InformationsquelleAutor William Pursell
Nein, es ist nicht immer falsch. Wenn Ihre Schleifenbedingung "while wir habe nicht versucht, zu Lesen hinter Dateiende", dann verwenden Sie
while (!feof(f))
. Dies ist jedoch nicht eine gemeinsame Schleife-Bedingung - in der Regel, die Sie testen möchten für etwas anderes (wie "kann ich mehr").while (!feof(f))
ist nicht falsch, es ist nur verwendet falsch.f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
oder (um dies zu testen)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
Wie gesagt, "nicht eine gemeinsame loop-Bedingung" hehe. Ich kann nicht wirklich denken, jedenfalls habe ich es brauchte, in der Regel bin ich daran interessiert, "konnte ich Lesen, was ich wollte" mit allen, die impliziert, Fehlerbehandlung
Wie gesagt, Sie selten wollen
while(!eof(f))
Genauer gesagt, die Bedingung "solange wir noch nicht versucht zu Lesen über das Ende der Datei und es gab keine read error"
feof
ist nicht zu erkennen das Ende der Datei; es ist zu bestimmen, ob ein Lesen war kurzfristig aufgrund eines Fehlers oder weil die Eingabe erschöpft ist.InformationsquelleAutor Erik
feof() gibt an, ob einer versucht hat zu Lesen, über das Ende der Datei. Das heißt, es hat wenig prädiktiven Effekt: wenn es wahr ist, sind Sie sicher, dass die nächste Eingabe-Vorgang fehl (Sie sind nicht sicher, dass der Vorherige fehlgeschlagen ist BTW), aber wenn es falsch ist, Sie sind nicht sicher, dass das nächste Eingabe-Vorgang wird erfolgreich sein. Mehr über, Eingabe-Vorgänge schlagen möglicherweise fehl aus anderen Gründen als dem Ende der Datei (ein format-Fehler für formatierte Eingaben, eine Reine IO-Ausfall -- Ausfall einer disk, network-timeout -- für alle input-Arten), so dass selbst wenn Sie könnten die Vorhersage über das Ende der Datei (und jeder, der versucht hat, zu implementieren, Ada, das ist vorausschauende, werden Ihnen sagen, es kann Komplex sein, wenn Sie brauchen, um zu überspringen, Leerzeichen, und dass es unerwünschte Wirkungen auf interaktive Geräte -- manchmal zwingt die Eingabe in der nächsten Zeile vor dem Start der Umgang mit dem vorherigen), Sie müssten in der Lage sein zu behandeln, ein Fehler.
Also die richtige idiom in C eine Schleife mit der IO-operation Erfolg als Schleifenbedingung, und testen Sie dann die Ursache des Fehlers. Zum Beispiel:
das erreichen der eof ist nicht unbedingt ein Fehler, aber nicht in der Lage zu tun, eine Eingabe wegen des eof ist. Und es ist unmöglich, in C zu erkennen zuverlässig die eof-ohne einen Eingabe-Vorgang fehlschlägt.
Stimmen letzten
else
nicht möglich, mitsizeof(line) >= 2
undfgets(line, sizeof(line), file)
aber möglich mit pathologischensize <= 0
undfgets(line, size, file)
. Vielleicht sogar möglich, mitsizeof(line) == 1
.Alle, die "prädiktiven Wert" sprechen... ich habe nie darüber nachgedacht, dass Art und Weise. In meiner Welt
feof(f)
nicht irgendetwas VORHERZUSAGEN. Es besagt, dass eine FRÜHERE operation hat, trifft das Ende der Datei. Nichts mehr, nichts weniger. Und wenn es keine vorangegangene operation (nur geöffnet), wird es nicht berichtet Ende der Datei, selbst wenn die Datei leer war, um mit zu beginnen. So, abgesehen von der Parallelität Erklärung in einer anderen Antwort erwähnt, ich glaube nicht, dass es aus irgendeinem Grund nicht zur Schleife auffeof(f)
."Lesen, maximal N bytes" - Anfrage, dass die Erträge null, ob da der "permanente" EOF oder weil keine Daten mehr verfügbar ist noch, ist kein Fehler. Während feof() kann nicht zuverlässig prognostizieren, dass künftige Anfragen werden die Ertragsdaten, es kann zuverlässig anzeigen, dass zukünftige Anfragen nicht. Vielleicht sollte es eine status-Funktion, anzuzeigen, dass "Es ist plausibel, dass die Zukunft Lesen Anforderungen sind erfolgreich", mit der Semantik, dass nach der Lektüre an eine normale Datei, eine Qualität, die Umsetzung sollte sagen Zukunft liest, sind unwahrscheinlich, erfolgreich zu sein abwesend einigen Grund zu glauben, Sie könnten.
InformationsquelleAutor AProgrammer
Super Antwort, ich habe gerade bemerkt, die gleiche Sache, weil ich versuche zu tun, eine Schleife so. Also, es ist falsch, dass Szenario, aber wenn Sie möchten, um eine Schleife, die anmutig endet am EOF, dies ist ein schöner Weg, es zu tun:
while( fgetc(fp) != EOF )
Stimmt, aber manchmal Sie nicht mit fgetc() zum Lesen von Dateien. Zum Beispiel, das Lesen von strukturierten Datensätzen, ich habe eine read-Funktion (in diesem Beispiel, wo es eine fgetc) die Fehler erkennt und liest genau einen Datensatz, aber es weiß nicht, wie viele Datensätze in der Datei. Ja, es ist falsch für fifos, oder jede andere Datei ändern kann, während Sie es öffnen.
while( read_structured_record( fp ) == 1 ) { ...
wäre ein idiomatischer Weise zu schreiben, dass (vorausgesetztread_structured_record
gibt die Anzahl der gelesenen Datensätze).Datei-I/O-Fehler können jederzeit passieren, so wird durch die Prüfung nicht das Ergebnis von
fgetc(fp)
man verpassen kann, und nicht alle Lesen die bytes.Dateien können wachsen, während Sie Sie zu Lesen; Sie können auch schrumpfen. Die Größe, die gegeben wird durch die one-time
stat()
Betrieb ist nicht zuverlässig über einen langen Zeitraum.InformationsquelleAutor tesch1