Alles, was ein Programm berechnet, verschwindet beim Beenden, sofern es das Ergebnis nicht aufschreibt. Cs Dateischnittstelle lebt in <stdio.h> - demselben Header wie printf - und sie ist bewusst ähnlich: Eine Datei ist ein Strom von Bytes, und die Funktionen, die auf dem Bildschirm arbeiten, haben Zwillinge, die ein Stromargument entgegennehmen.
Der Handle-Typ ist FILE *. Du siehst nie in ein FILE hinein; es ist ein undurchsichtiger Typ (siehe typedef), und du hältst immer nur einen Zeiger darauf.
Die drei Schritte
Jede Dateioperation hat dieselbe Form: öffnen, benutzen, schließen.
Die NULL-Prüfung ist keine optionale Paranoia. fopen schlägt fehl, wann immer die Datei nicht existiert (im Lesemodus), das Verzeichnis nicht beschreibbar ist, der Pfad falsch ist oder dem Prozess die Dateihandles ausgegangen sind - und jeder dieser Fälle liefert NULL. Ein NULL-Handle zu benutzen ist ein Segmentierungsfehler.
Den Fehler nach stderr statt nach stdout zu schicken ist die Konvention: Es ist ungepuffert und lässt sich getrennt von der eigentlichen Ausgabe umleiten.
fopen-Modi
Das zweite Argument ist eine kurze Moduszeichenkette. Sie falsch zu wählen ist der zerstörerischste Fehler in diesem ganzen Bereich, denn "w" leert eine vorhandene Datei stillschweigend.
| Modus | Lesen | Schreiben | Wenn die Datei existiert | Wenn nicht |
|---|---|---|---|---|
"r" | ja | nein | öffnet am Anfang | scheitert, liefert NULL |
"w" | nein | ja | auf leer abgeschnitten | wird angelegt |
"a" | nein | ja | schreibt nur am Ende | wird angelegt |
"r+" | ja | ja | öffnet am Anfang | scheitert, liefert NULL |
"w+" | ja | ja | auf leer abgeschnitten | wird angelegt |
"a+" | ja | ja | liest überall, schreibt am Ende | wird angelegt |
Hänge an jeden davon ein b an ("rb", "wb", "ab+") für den Binärmodus - dazu mehr am Ende.
Zwei Regeln, die echten Datenverlust verhindern:
- Nimm
"r", wenn du lesen willst."r+"auf einem vertippten Dateinamen scheitert sicher;"w+"legt eine leere Datei an, und du hast nichts als Zeit verloren."w"auf dem richtigen Dateinamen, wenn du"r"meintest, zerstört die Daten. - Nimm
"a"für Protokolle. Jedesfprintflandet am Ende, unabhängig davon, wo der Strom positioniert war, und genau das will ein Protokoll.
Schreiben und Lesen in einem Programm
Der Editor unten führt einen vollständigen Rundlauf aus - er legt eine Datei an, schreibt Datensätze hinein, schließt sie, öffnet sie erneut zum Lesen und gibt aus, was er findet.
fprintf und fscanf sind printf und scanf mit einem Strom als erstem Argument; alles an ihren Formatangaben ist identisch, einschließlich der Breite %31s, die name vor dem Überlaufen bewahrt.
Die Schleifenbedingung lautet == 2, die Anzahl der Elemente, nach denen das Format fragt. Stattdessen gegen EOF zu prüfen ist ein klassischer Bug: Eine fehlerhafte Zeile lässt fscanf 0 liefern, nicht EOF, und die Schleife dreht sich ewig über einer Eingabe, die sie nicht verbrauchen kann.
Zeilenweises Lesen mit fgets
fscanf ist bequem für streng formatierte Daten. Für Textdateien - Konfiguration, Protokolle, CSV, alles, was ein Mensch geschrieben hat - lies ganze Zeilen. Diese Schleife ist die kanonische:
Was fgets zur richtigen Voreinstellung macht:
- Es nimmt die Puffergröße entgegen, kann also nicht überlaufen. Übergib
sizeof line, und der Aufruf bleibt korrekt, wenn du das Array vergrößerst. - Es liefert am Dateiende
NULLoder bei einem Fehler, was eine saubere Schleifenbedingung ist. - Es behält den Zeilenumbruch, wenn die Zeile in den Puffer passte. Das ist nützlich - kein
'\n'im Erhaltenen bedeutet, dass die Zeile länger als dein Puffer war und der Rest noch wartet.strcspn(line, "\n")findet den Index des Zeilenumbruchs (oder die Länge der Zeichenkette, wenn es keinen gibt), dort'\0'zuzuweisen schneidet ihn also in beiden Fällen ab.
Um ein echtes Dateiende von einem Fehler zu unterscheiden, frage nach der Schleife:
if (ferror(in)) {
fprintf(stderr, "Lesefehler\n");
} else if (feof(in)) {
/* normales Ende */
}
Schreibe nicht while (!feof(fp)) als Schleifenbedingung. feof wird erst wahr, nachdem ein Lesezugriff bereits fehlgeschlagen ist, diese Schleife verarbeitet also den letzten Pufferinhalt ein zusätzliches Mal. Prüfe stattdessen den Rückgabewert der Lesefunktion - wie es beide Schleifen oben tun.
Zeichen für Zeichen: fgetc und fputc
Für Arbeit auf Byteebene - Zeichen zählen, eine Datei umformen, kopieren - behandeln fgetc und fputc ein Zeichen pro Aufruf.
Ein Detail, auf das es ankommt: c ist als int deklariert, nicht als char. fgetc liefert ein int, damit es jeden möglichen Bytewert und den eigenständigen Wächter EOF (der -1 ist) zurückgeben kann. Es in einem char zu speichern macht den Vergleich gegen EOF entweder immer falsch oder fälschlich wahr für das Byte 0xFF, je nachdem, ob char auf deiner Plattform vorzeichenbehaftet ist. Das ist eine der ältesten Fallen in C.
Fehler richtig prüfen
Ein produktionsreifer Lesezugriff sieht so aus:
errno hält einen Code, der den letzten Fehlschlag beschreibt, und strerror macht daraus einen Satz; perror gibt deine Meldung plus diesen Satz in einem Aufruf aus. Beide brauchen <errno.h> beziehungsweise <string.h>.
Auch fclose kann fehlschlagen - es leert gepufferte Daten, und der Schreibzugriff passt womöglich nicht auf die Platte -, prüfe es also bei allem Wichtigen:
if (fclose(fp) != 0) {
fprintf(stderr, "Leeren und Schließen fehlgeschlagen\n");
}
Der Binärmodus in einem Absatz
Der Textmodus darf Zeilenenden umsetzen (unter Windows wird \n beim Schreiben zu \r\n und beim Lesen zurück) und darf bestimmte Bytes besonders behandeln. Für Daten, die kein Text sind - ein Bild, eine wörtlich abgelegte Struktur, ein komprimierter Klumpen -, öffne mit b und benutze fread/fwrite, die rohe Bytes bewegen:
fwrite(ptr, size, count, fp) schreibt count Elemente von je size Bytes und liefert, wie viele es geschafft hat; fread spiegelt das. Sei dir bewusst, dass eine so geschriebene Datei an die Maschine gebunden ist, die sie schrieb - Füllbytes der Struktur, Ganzzahlgröße und Byte-Reihenfolge sickern alle in die Bytes -, sie taugt also für einen Zwischenspeicher oder eine Arbeitsdatei und ist falsch für ein Format, das andere Programme lesen müssen.
Häufige Fehler
fopennicht aufNULLprüfen. Der folgende Absturz wird dem Lesen angelastet, nicht dem Öffnen.- Mit
"w"öffnen, wenn du"r"meintest. Die Datei ist geleert, bevor du es merkst. fclosevergessen. Gepufferte Ausgabe geht verloren, und jede nicht geschlossene Datei leckt ein Handle.while (!feof(fp)). Verarbeitet die letzte Zeile zweimal. Prüfe stattdessen den Leseaufruf.char c = fgetc(fp). Zerstört denEOF-Vergleich. Nimmint.fscanf("%s", buf)ohne Breite. Derselbe Pufferüberlauf wie bei scanf über die Tastatur.- Relative Pfade.
fopen("data.txt", "r")sucht im Arbeitsverzeichnis, nicht neben der ausführbaren Datei. Wenn eine Datei „verschwindet", liegt es meist daran.
Häufig gestellte Fragen
Wie öffnet man in C eine Datei?
FILE *fp = fopen("data.txt", "r"); öffnet eine Datei zum Lesen und liefert ein FILE *-Handle oder NULL, wenn es fehlschlug. Prüfe immer auf NULL, bevor du das Handle benutzt, und rufe fclose(fp) auf, wenn du fertig bist.
Welche fopen-Modi gibt es in C?
"r" lesen (die Datei muss existieren), "w" schreiben (legt an oder leert eine vorhandene Datei), "a" anhängen (legt an, schreibt am Ende). Ein + macht jeden davon lesend und schreibend: "r+", "w+", "a+". Ein b ("rb", "wb") öffnet im Binärmodus.
Wie liest man in C eine Datei Zeile für Zeile?
Benutze fgets in einer while-Schleife: while (fgets(line, sizeof line, fp) != NULL) { ... }. Es stoppt an jedem Zeilenumbruch oder wenn der Puffer voll ist, liefert am Dateiende NULL und kann nicht überlaufen, weil du die Puffergröße übergibst.
Warum ist meine Datei nach dem Schreiben in C leer?
Meist hast du fclose vergessen. Die Ausgabe wird gepuffert, Daten können also noch im Speicher liegen, wenn das Programm unnormal endet. fclose leert den Puffer und schließt; fflush(fp) leert ohne zu schließen. Die andere Ursache ist ein zweites Öffnen mit "w", das die eben geschriebene Datei abschneidet.