Ich habe in letzter Zeit in vielen Posts Leute gesehen, die versucht haben, solche Dateien zu lesen:
#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;
}
Was ist los mit dieser Schleife?
feof()
eine Schleife zu steuernAntworten:
Ich möchte eine abstrakte Perspektive auf hoher Ebene bieten.
Parallelität und Gleichzeitigkeit
E / A-Vorgänge interagieren mit der Umgebung. Die Umgebung ist nicht Teil Ihres Programms und nicht unter Ihrer Kontrolle. Die Umgebung existiert wirklich "gleichzeitig" mit Ihrem Programm. Wie bei allen gleichzeitigen Dingen sind Fragen zum "aktuellen Zustand" nicht sinnvoll: Es gibt kein Konzept der "Gleichzeitigkeit" zwischen gleichzeitigen Ereignissen. Viele Eigenschaften von Staat einfach nicht existieren gleichzeitig.
Lassen Sie mich das präzisieren: Angenommen, Sie möchten fragen: "Haben Sie mehr Daten?". Sie können dies von einem gleichzeitigen Container oder von Ihrem E / A-System verlangen. Aber die Antwort ist im Allgemeinen nicht handlungsfähig und daher bedeutungslos. Was ist, wenn der Container "Ja" sagt? Wenn Sie versuchen zu lesen, enthält er möglicherweise keine Daten mehr. Wenn die Antwort "Nein" lautet, sind zum Zeitpunkt des Leseversuchs möglicherweise Daten eingetroffen. Die Schlussfolgerung ist, dass es einfach gibtKeine Eigenschaft wie "Ich habe Daten", da Sie auf eine mögliche Antwort nicht sinnvoll reagieren können. (Bei gepufferten Eingaben ist die Situation etwas besser, da Sie möglicherweise ein "Ja, ich habe Daten" erhalten, das eine Art Garantie darstellt, aber Sie müssten immer noch in der Lage sein, den umgekehrten Fall zu behandeln. Und bei der Ausgabe die Situation ist sicherlich genauso schlimm wie ich beschrieben habe: Man weiß nie, ob diese Festplatte oder dieser Netzwerkpuffer voll ist.)
So schließen wir , dass es unmöglich ist, und in der Tat un vernünftig , ein I / O - System zu fragen , ob es sein wird , kann eine E / A - Operation auszuführen. Die einzige Möglichkeit, mit ihm zu interagieren (genau wie mit einem gleichzeitigen Container), besteht darin , den Vorgang zu versuchen und zu überprüfen, ob er erfolgreich war oder fehlgeschlagen ist. In dem Moment, in dem Sie mit der Umgebung interagieren, können Sie dann und nur dann wissen, ob die Interaktion tatsächlich möglich war, und an diesem Punkt müssen Sie sich zur Durchführung der Interaktion verpflichten. (Dies ist ein "Synchronisationspunkt", wenn Sie so wollen.)
EOF
Jetzt kommen wir zu EOF. EOF ist die Antwort, die Sie von einem versuchten E / A-Vorgang erhalten. Dies bedeutet, dass Sie versucht haben, etwas zu lesen oder zu schreiben, dabei jedoch keine Daten gelesen oder geschrieben haben und stattdessen das Ende der Eingabe oder Ausgabe festgestellt wurde. Dies gilt im Wesentlichen für alle E / A-APIs, unabhängig davon, ob es sich um die C-Standardbibliothek, C ++ - Iostreams oder andere Bibliotheken handelt. Solange die E / A-Vorgänge erfolgreich sind, können Sie einfach nicht wissen, ob weitere zukünftige Vorgänge erfolgreich sein werden. Sie müssen immer zuerst den Vorgang versuchen und dann auf Erfolg oder Misserfolg reagieren.
Beispiele
Beachten Sie in jedem der Beispiele sorgfältig, dass wir zuerst die E / A-Operation versuchen und dann das Ergebnis verwenden, wenn es gültig ist. Beachten Sie außerdem, dass wir immer das Ergebnis der E / A-Operation verwenden müssen, obwohl das Ergebnis in jedem Beispiel unterschiedliche Formen und Formen annimmt.
C stdio, aus einer Datei lesen:
Das Ergebnis, das wir verwenden müssen, ist
n
die Anzahl der gelesenen Elemente (die möglicherweise nur Null betragen).C stdio ,
scanf
:Das Ergebnis, das wir verwenden müssen, ist der Rückgabewert von
scanf
, die Anzahl der konvertierten Elemente.C ++, iostreams formatierte Extraktion:
Das Ergebnis, das wir verwenden müssen, ist
std::cin
selbst, das in einem booleschen Kontext ausgewertet werden kann und uns sagt, ob sich der Stream noch imgood()
Status befindet.C ++, iostreams getline:
Das Ergebnis, das wir verwenden müssen, ist wieder
std::cin
wie zuvor.POSIX,
write(2)
um einen Puffer zu leeren:Das Ergebnis, das wir hier verwenden, ist
k
die Anzahl der geschriebenen Bytes. Der Punkt hier ist, dass wir nur wissen können, wie viele Bytes nach dem Schreibvorgang geschrieben wurden.POSIX
getline()
Das Ergebnis, das wir verwenden müssen, ist
nbytes
die Anzahl der Bytes bis einschließlich der neuen Zeile (oder EOF, wenn die Datei nicht mit einer neuen Zeile endete).Beachten Sie, dass die Funktion explizit
-1
(und nicht EOF!) Zurückgibt, wenn ein Fehler auftritt oder EOF erreicht.Sie werden feststellen, dass wir das eigentliche Wort "EOF" sehr selten buchstabieren. Normalerweise erkennen wir den Fehlerzustand auf eine andere Weise, die für uns unmittelbar interessanter ist (z. B. wenn nicht so viele E / A-Vorgänge ausgeführt werden, wie wir es gewünscht hatten). In jedem Beispiel gibt es eine API-Funktion, die uns explizit mitteilen könnte, dass der EOF-Status aufgetreten ist, aber dies ist in der Tat keine besonders nützliche Information. Es ist viel mehr ein Detail, als uns oft wichtig ist. Entscheidend ist, ob die E / A erfolgreich war, mehr als wie sie fehlgeschlagen ist.
Ein letztes Beispiel, das den EOF-Status tatsächlich abfragt: Angenommen, Sie haben eine Zeichenfolge und möchten testen, ob sie eine Ganzzahl in ihrer Gesamtheit darstellt, ohne zusätzliche Bits am Ende außer Leerzeichen. Mit C ++ iostreams geht es so:
Wir verwenden hier zwei Ergebnisse. Das erste ist
iss
das Stream-Objekt selbst, um zu überprüfen, ob die formatierte Extraktionvalue
erfolgreich war. Nachdem wir jedoch auch Leerzeichen verbraucht haben, führen wir eine weitere E / A / Operation ausiss.get()
und erwarten, dass diese als EOF fehlschlägt. Dies ist der Fall, wenn die gesamte Zeichenfolge bereits von der formatierten Extraktion verbraucht wurde.In der C-Standardbibliothek können Sie mit den
strto*l
Funktionen etwas Ähnliches erreichen, indem Sie überprüfen, ob der Endzeiger das Ende der Eingabezeichenfolge erreicht hat.Die Antwort
while(!feof)
ist falsch, weil es auf etwas testet, das irrelevant ist und nicht auf etwas testet, das Sie wissen müssen. Das Ergebnis ist, dass Sie fälschlicherweise Code ausführen, der davon ausgeht, dass er auf Daten zugreift, die erfolgreich gelesen wurden, obwohl dies tatsächlich nie geschehen ist.quelle
feof()
fragt nicht "das E / A-System, ob es mehr Daten hat".feof()
Laut der (Linux-) Manpage : "Testet den Indikator für das Dateiende für den Stream, auf den der Stream zeigt, und gibt ungleich Null zurück, wenn er gesetzt ist." (auch ein expliziter Aufruf vonclearerr()
ist die einzige Möglichkeit, diesen Indikator zurückzusetzen); In dieser Hinsicht ist William Pursells Antwort viel besser.Es ist falsch, weil es (ohne Lesefehler) einmal mehr in die Schleife eintritt, als der Autor erwartet. Wenn ein Lesefehler auftritt, wird die Schleife niemals beendet.
Betrachten Sie den folgenden Code:
Dieses Programm druckt konsistent ein Zeichen, das größer als die Anzahl der Zeichen im Eingabestream ist (unter der Annahme, dass keine Lesefehler vorliegen). Betrachten Sie den Fall, in dem der Eingabestream leer ist:
In diesem Fall
feof()
wird aufgerufen, bevor Daten gelesen wurden, sodass false zurückgegeben wird. Die Schleife wird eingegeben,fgetc()
aufgerufen (und zurückgegebenEOF
) und die Anzahl wird erhöht. Dannfeof()
wird aufgerufen und gibt true zurück, wodurch die Schleife abgebrochen wird.Dies geschieht in all diesen Fällen.
feof()
gibt true erst zurück, nachdem ein Lesevorgang im Stream das Dateiende erreicht hat. Der Zweck vonfeof()
ist NICHT zu überprüfen, ob der nächste Lesevorgang das Ende der Datei erreicht. Der Zweck vonfeof()
besteht darin, zwischen einem Lesefehler und dem Ende der Datei zu unterscheiden. Wennfread()
0 zurückgegeben wird, müssen Sie mitfeof
/ferror
entscheiden, ob ein Fehler aufgetreten ist oder ob alle Daten verbraucht wurden. Ähnliches gilt, wennfgetc
zurückgegeben wirdEOF
.feof()
nur ist nützlich , nachdem fread Null oder zurückgekehrtfgetc
ist zurückgekehrtEOF
. Bevor dies geschieht,feof()
wird immer 0 zurückgegeben.Es ist immer notwendig, den Rückgabewert eines Lesevorgangs (entweder ein
fread()
oder einfscanf()
oder einfgetc()
) vor dem Aufruf zu überprüfenfeof()
.Betrachten Sie noch schlimmer den Fall, in dem ein Lesefehler auftritt. In diesem Fall wird
fgetc()
return zurückgegebenEOF
,feof()
false zurückgegeben, und die Schleife wird niemals beendet. In allen Fällen, in denenwhile(!feof(p))
verwendet wird, muss mindestens eine Überprüfung innerhalb der Schleife durchgeführt werdenferror()
, oder zumindest sollte die while-Bedingung durch ersetzt werden,while(!feof(p) && !ferror(p))
oder es besteht die sehr reale Möglichkeit einer Endlosschleife, die wahrscheinlich alle Arten von Müll ausspuckt ungültige Daten werden verarbeitet.So in der Zusammenfassung, obwohl ich kann nicht Staat mit Sicherheit , dass es nie eine Situation , in der es semantisch Schreib richtig sein kann „
while(!feof(f))
“ (obwohl es muss mit einer Pause erneut die Kontrolle innerhalb der Schleife sein , eine Endlosschleife auf einem Lesefehler zu vermeiden ) ist es so, dass es mit ziemlicher Sicherheit immer falsch ist. Und selbst wenn jemals ein Fall auftauchte, in dem es richtig wäre, ist es so idiomatisch falsch, dass es nicht der richtige Weg wäre, den Code zu schreiben. Jeder, der diesen Code sieht, sollte sofort zögern und sagen: "Das ist ein Fehler." Und möglicherweise den Autor schlagen (es sei denn, der Autor ist Ihr Chef. In diesem Fall wird Diskretion empfohlen.)quelle
feof(file) || ferror(file)
, also ist es sehr unterschiedlich. Diese Frage soll jedoch nicht auf C ++ anwendbar sein.Nein, das ist nicht immer falsch. Wenn Ihre Schleifenbedingung "während wir nicht versucht haben, das vergangene Dateiende zu lesen" lautet, verwenden Sie
while (!feof(f))
. Dies ist jedoch keine übliche Schleifenbedingung - normalerweise möchten Sie auf etwas anderes testen (z. B. "Kann ich mehr lesen").while (!feof(f))
nicht falsch, es einfach ist gebrauchte falsch.quelle
f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
oder (werde das testen)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
while(!eof(f))
feof
nicht darum, das Ende der Datei zu erkennen. Es geht darum festzustellen, ob ein Lesevorgang aufgrund eines Fehlers kurz war oder weil die Eingabe erschöpft ist.feof()
zeigt an, ob versucht wurde, über das Dateiende hinaus zu lesen. Das bedeutet, dass es wenig prädiktive Wirkung hat: Wenn es wahr ist, sind Sie sicher, dass die nächste Eingabeoperation fehlschlägt (Sie sind nicht sicher, ob die vorherige BTW fehlgeschlagen ist), aber wenn es falsch ist, sind Sie nicht sicher, ob die nächste Eingabe fehlschlägt Operation wird erfolgreich sein. Darüber hinaus können Eingabevorgänge aus anderen Gründen als dem Dateiende fehlschlagen (ein Formatfehler für formatierte Eingaben, ein reiner E / A-Fehler - Festplattenfehler, Netzwerk-Timeout - für alle Eingabearten), selbst wenn Sie dies vorhersagen können Das Ende der Datei (und jeder, der versucht hat, Ada One zu implementieren, was voraussagend ist, wird Ihnen sagen, dass es komplex sein kann, wenn Sie Leerzeichen überspringen müssen, und dass es unerwünschte Auswirkungen auf interaktive Geräte hat - manchmal erzwingt es die Eingabe des nächsten Zeile vor Beginn der Behandlung der vorherigen),Die korrekte Redewendung in C besteht also darin, eine Schleife mit dem Erfolg der E / A-Operation als Schleifenbedingung durchzuführen und dann die Fehlerursache zu testen. Zum Beispiel:
quelle
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
.feof(f)
sagt nichts voraus. Es heißt, dass eine VORHERIGE Operation das Ende der Datei erreicht hat. Nicht mehr, nicht weniger. Und wenn es keinen vorherigen Vorgang gab (nur geöffnet), wird das Dateiende nicht gemeldet, selbst wenn die Datei zu Beginn leer war. Abgesehen von der Erklärung der Parallelität in einer anderen Antwort oben glaube ich nicht, dass es einen Grund gibt, nicht weiterzumachenfeof(f)
.feof()
ist nicht sehr intuitiv. Meiner sehr bescheidenen Meinung nach sollte derFILE
Status "Dateiende" auf "" gesetzt werden,true
wenn eine Leseoperation dazu führt, dass das Dateiende erreicht wird. Stattdessen müssen Sie nach jedem Lesevorgang manuell prüfen, ob das Dateiende erreicht ist. So etwas funktioniert beispielsweise, wenn Sie aus einer Textdatei lesen mitfgetc()
:Es wäre großartig, wenn so etwas stattdessen funktionieren würde:
quelle
printf("%c", fgetc(in));
? Das ist undefiniertes Verhalten.fgetc()
kehrt zurückint
, nichtchar
.while( (c = getchar()) != EOF)
sehr viel "so etwas" ist.while( (c = getchar()) != EOF)
Funktioniert auf einem meiner Desktops mit GNU C 10.1.0, schlägt jedoch auf meinem Raspberry Pi 4 mit GNU C 9.3.0 fehl. Auf meinem RPi4 erkennt es das Dateiende nicht und macht einfach weiter.char c
zuint c
Werken! Vielen Dank!!