Dateiein- und -ausgabe in C
fgets. Speichere Arrays und Datensätze mit fread und fwrite in Binärdateien, entwirf Dateiformate, die auch auf anderen Rechnern korrekt gelesen werden, und springe mit fseek zu einem beliebigen Datensatz. Dabei prüfst du jedes Ergebnis, erklärst Fehler mit errno und gibst Ressourcen frei, damit deine Programme bei Fehlern sicher reagieren und ihr Ergebnis wahrheitsgemäß melden.AnfängerEinheit 7 von 8 in Kurs: C-Programmierung – Zeiger, Speicherverwaltung und sicheres C
Was du lernst
- Erklären, wie C-Programme über gepufferte Datenströme auf Dateien zugreifen, Dateien im passenden Modus öffnen und schließen sowie die drei Standarddatenströme mit Umleitung über die Kommandozeile verwenden.
- Datenströme, Pufferung und den Unterschied zwischen Text- und Binärmodus erklärenBeschreiben, was ein Datenstrom und ein
FILE *sind, wie Pufferung und Dateiposition funktionieren und wie sich Text- und Binärmodus unter Windows und Unix-artigen Systemen unterscheiden. - Dateien mit fopen und fclose öffnen und schließenEine Datei im zur Aufgabe passenden Modus öffnen, prüfen, ob sie geöffnet wurde, Dateien anhand ihres Pfads finden und nach getaner Arbeit jeden Datenstrom schließen.
- stdin, stdout und stderr mit Umleitungen und Pipes verwendenDie drei Datenströme verwenden, mit denen jedes C-Programm startet, Ergebnisse und Fehlermeldungen an die richtige Stelle senden und es Nutzenden ermöglichen, sie über die Kommandozeile umzuleiten.
- Datenströme, Pufferung und den Unterschied zwischen Text- und Binärmodus erklären
- Textdateien zeichenweise, durch formatiertes Lesen und Schreiben sowie zeilenweise verarbeiten, für jeden Fall ein sicheres Vorgehen wählen und jedes Ergebnis prüfen.
- Zeichenweise lesen und schreibenfgetc und fputc mit einer int-Variablen verwenden, am Dateiende korrekt anhalten und Text Zeichen für Zeichen zählen, kopieren und umwandeln.
- Formatierten Text mit fprintf schreiben und mit fscanf lesenZahlen und Zeichenketten in einem gewählten Format in Dateien schreiben, Dateinamen mit sprintf erzeugen und formatierte Daten mit fscanf lesen und dabei prüfen, wie viele Elemente konvertiert wurden.
- Zeilen mit fgets sicher lesenText mit fgets zeilenweise lesen, Zeilenumbrüche, lange Zeilen und Windows-Zeilenenden behandeln und jede Zeile nach dem Lesen parsen.
- Zeichenweise lesen und schreiben
- Rohe Bytes, Arrays und Strukturen mit fwrite und fread schreiben und lesen sowie Binärdateiformate entwerfen, die auch auf anderen Rechnern korrekt gelesen werden.
- Binärdaten mit fread lesen und mit fwrite schreibenfread und fwrite mit der richtigen Elementgröße und -anzahl verwenden, Dateien blockweise kopieren, Dateitypen an ihren ersten Bytes erkennen sowie Arrays und Strukturen speichern.
- Rechnerübergreifend verwendbare Binärformate entwerfenErklären, wie Typgrößen, Byte-Reihenfolge und Füllbytes in Strukturen rohe Binärdateien rechnerabhängig machen, und Felder aus mehreren Bytes in einem festgelegten Format lesen und schreiben.
- Binärdaten mit fread lesen und mit fwrite schreiben
- Die Dateiposition ermitteln und setzen, um direkt zu den benötigten Daten zu springen, und Datensätze fester Größe an ihrer bestehenden Position aktualisieren, ohne die ganze Datei neu zu schreiben.
- Die Dateiposition mit ftell, fseek und rewind ermitteln und setzenDie aktuelle Position ausgeben, Positionen relativ zum Anfang, zur aktuellen Stelle oder zum Ende ansteuern, Datensatzversätze und Dateigrößen berechnen und die Einschränkungen für Textströme kennen.
- Datensätze fester Größe an ihrer bestehenden Dateiposition aktualisierenEine Datei zum Lesen und Schreiben öffnen, die Regel für den Wechsel zwischen beiden beachten und Datensätze ändern, hinzufügen oder entfernen, ohne die Datei zu beschädigen.
- Die Dateiposition mit ftell, fseek und rewind ermitteln und setzen
- Das Ergebnis jeder Dateioperation prüfen, Nutzenden Fehler mit errno erklären und jede Ressource freigeben, damit Programme bei Fehlern sicher reagieren und Erfolg wahrheitsgemäß melden.
- Jedes Ergebnis prüfen und das Dateiende von Fehlern unterscheidenWissen, wie jede stdio-Funktion einen Fehler signalisiert, nach dem Ende einer Leseschleife feof und ferror verwenden und Schreibfehler erkennen, die erst beim Schreiben des Puffers auftreten.
- Fehler mit errno, perror und strerror erklärenMit errno herausfinden, warum ein Bibliotheksaufruf fehlgeschlagen ist, und klare Fehlermeldungen ausgeben, die das Programm, die Datei und den Grund nennen.
- Ressourcen freigeben und das Ergebnis meldenJeden Datenstrom auf jedem Programmpfad schließen, keine halb geschriebene Ausgabe zurücklassen und einen Rückgabestatus liefern, der das Ergebnis wahrheitsgemäß angibt.
- Jedes Ergebnis prüfen und das Dateiende von Fehlern unterscheiden
Dateiein- und -ausgabe in C einfach erklärt
1Dateien als Datenströme öffnen und Programme mit ihren Ein- und Ausgabekanälen verbinden
Dateien sind für ein Programm über Datenströme zugänglich, die Zugriff, Pufferung und Position steuern. Die Wahl eines Modus kann alte Daten erhalten oder löschen oder jeden Schreibvorgang ans Ende zwingen. Deshalb gehört das Öffnen zum Entwurf des Programms. Die drei Standarddatenströme erweitern dieselbe Schnittstelle auf Terminals, umgeleitete Dateien und Pipes. Wenn du ihre Verbindungen verstehst, kannst du eine Verarbeitungsroutine schreiben, die in mehreren Situationen funktioniert und Diagnosemeldungen von den eigentlichen Ergebnissen trennt.
1.1 Datenströme, Pufferung und den Unterschied zwischen Text- und Binärmodus erklären
Eine Datei besteht aus gespeicherten Daten; ein Datenstrom ist der Kanal, über den ein laufendes Programm darauf zugreift. Dieselben Datenstromfunktionen können auch mit einem Terminal oder einer Pipe arbeiten. Binde <stdio.h> ein und speichere den Datenstromzeiger in einer Variablen vom Typ FILE *.
Was der Datenstrom speichert
Das FILE-Objekt der Bibliothek verwaltet einen Puffer, die aktuelle Dateiposition sowie den Dateiende- und den Fehlerindikator. Übergib seinen Zeiger an Funktionen wie fgetc und fprintf; lies oder ändere seine Felder nicht. Ein Lese- oder Schreibvorgang verschiebt die Position über die verarbeiteten Daten hinaus. Liest du in einer Binärdatei, die mit "hello\nworld\n" beginnt, die ersten sechs Bytes, beginnt der nächste Lesevorgang beim w.
Pufferung fasst kleine Vorgänge zu größeren Übertragungen zusammen. Ein erfolgreicher Schreibvorgang kann bedeuten, dass Daten einen Speicherpuffer erreicht haben, die Übertragung an das Betriebssystem aber noch aussteht. fflush(fp) sendet ausstehende Ausgaben an das Betriebssystem. fclose(fp) tut dies, bevor es den Datenstrom freigibt. Keiner der beiden Aufrufe garantiert, dass Daten einen plötzlichen Stromausfall überstehen.
| Pufferungsart | Wann die Ausgabe übertragen wird |
|---|---|
| Vollständig gepuffert | Wenn der Puffer voll ist oder ausdrücklich geleert beziehungsweise geschlossen wird |
| Zeilengepuffert | Außerdem, wenn ein Zeilenumbruch geschrieben wird |
| Ungepuffert | So schnell wie möglich |
Gewöhnliche Dateien, die mit fopen geöffnet werden, sind vollständig gepuffert. Interaktive Geräte können anders gepuffert sein. Ein Terminal oder eine Pipe bietet keine gewöhnlichen Dateipositionen, zwischen denen vor- und zurückgesprungen werden kann.
Text- oder Binärmodus wählen
Der Textmodus verarbeitet menschenlesbare Zeilen. Der Binärmodus erhält Bytewerte ohne Textumwandlungen. Füge dem Öffnungsmodus für Binärdaten b hinzu: "rb" liest sie und "wb" schreibt sie. Bilder, komprimierte Daten und rohe Datensätze benötigen den Binärmodus, auch wenn eine Variante im Textmodus auf deinem eigenen Rechner zu funktionieren scheint.
Unter Linux und macOS ist ein Zeilenumbruch ein LF-Byte, 0x0A, und die beiden Modi verhalten sich normalerweise gleich. Unter Windows wandelt die Textausgabe '\n' in die beiden Bytes CR LF, 0x0D 0x0A, um. Die Texteingabe wandelt CR LF wieder in ein '\n' um. Microsofts Texteingabe erkennt außerdem 0x1A, Strg+Z, als Dateiende.
Wird zum Beispiel "a\nb\nc\n" im Textmodus geschrieben, entstehen unter Linux oder macOS sechs Bytes und unter Windows neun. Drei Buchstaben ergeben drei Bytes; drei Windows-Zeilenenden ergeben sechs. Liest du die Windows-Datei unter Windows im Textmodus, erhältst du sechs Zeichen, während der Binärmodus alle neun Bytes sichtbar macht.
Die Bytes der Datei vom Text unterscheiden
Eine unter Windows erstellte und unter Linux gelesene Zeile kann "\r\n" behalten. Wird nur '\n' entfernt, bleibt '\r' stehen, wodurch ein Vergleich mit "yes" fehlschlagen kann. Eine Position in einem Textstrom kann auch den Zustand der Umwandlung widerspiegeln. Speichere deshalb Positionen in Textströmen und stelle sie wieder her, statt Byteversätze zu berechnen.
C garantiert die genaue Rückgabe geschriebenen Textes für druckbare Zeichen, Tabulatoren und Zeilenumbrüche, wenn unmittelbar vor einem Zeilenumbruch kein Leerzeichen steht und die letzte Zeile mit einem Zeilenumbruch endet. Andere Bytefolgen gehören in Binärströme. C-Bytes haben mindestens acht Bit; Beispiele mit Bytewerten von 0 bis 255 und gängigen Dateiformaten verwenden Bytes mit acht Bit.
Wichtige Erkenntnisse
- Ein
FILE *gibt Bibliotheksfunktionen Zugriff auf einen Datenstrom und seinen Zustand. - Gepufferte Ausgaben können beim Leeren des Puffers oder beim Schließen fehlschlagen; prüfe diese Ergebnisse.
- Der Windows-Textmodus wandelt Zeilenenden um; der Binärmodus erhält die Bytes.
- Speichere Positionen in Textströmen; berechne Versätze in Binärdateien.
- Gehe bei der Interpretation gängiger Binärformate ausdrücklich von Bytes mit acht Bit aus.
1.2 Dateien mit fopen und fclose öffnen und schließen
Wähle einen Öffnungsmodus, bevor du Verarbeitungscode schreibst. Der Modus bestimmt, ob die Datei existieren muss, ob ihr Inhalt erhalten bleibt und wohin geschrieben wird. fopen wirkt sofort auf die Datei: Ein erfolgreiches Öffnen mit "w" löscht den Inhalt einer vorhandenen Datei, selbst wenn später nichts geschrieben wird.
Den passenden Modus wählen
| Modus | Vorhandene Datei | Fehlende Datei | Zugriff |
|---|---|---|---|
"r" | Behält den Inhalt | Schlägt fehl | Lesen ab Anfang |
"w" | Löscht den Inhalt | Erstellt die Datei | Schreiben ab Anfang |
"a" | Behält den Inhalt | Erstellt die Datei | Schreiben am Ende |
"r+" | Behält den Inhalt | Schlägt fehl | Lesen und Schreiben |
"w+" | Löscht den Inhalt | Erstellt die Datei | Lesen und Schreiben |
"a+" | Behält den Inhalt | Erstellt die Datei | Überall lesen; am Ende schreiben |
Füge b für den Binärmodus hinzu; "r+b" und "rb+" bedeuten dasselbe. C11 ergänzt das exklusive Erstellen mit x in Schreibmodi wie "wx" und "wbx": Das Öffnen schlägt fehl, wenn die Datei existiert. Verwende einen dokumentierten Modusstring; zusätzliche Modusbuchstaben sind nicht portabel.
Vor der Verwendung des Zeigers prüfen
fopen gibt bei Erfolg einen neuen Datenstromzeiger und bei einem Fehler NULL zurück. Fehlende Dateien, nicht zugängliche Pfade und fehlende Verzeichnisse können Fehler verursachen.
FILE *fp = fopen("scores.txt", "r");
if (fp == NULL) {
fprintf(stderr, "Cannot open scores.txt\n");
return EXIT_FAILURE;
}
/* Read through fp here. */
if (fclose(fp) == EOF) {
fprintf(stderr, "Cannot close scores.txt\n");
return EXIT_FAILURE;
}
Der Ausschnitt verwendet <stdio.h> und <stdlib.h>. Funktionen wie fgetc, fprintf und fclose benötigen einen gültigen Datenstrom; die Übergabe von NULL führt zu undefiniertem Verhalten. fflush(NULL) ist eine bewusst vorgesehene Ausnahme, die die Puffer aller Ausgabeströme leert.
fclose gibt bei Erfolg 0 und bei einem Fehler EOF zurück. Nach dem Aufruf ist der Datenstrom abgeschlossen, auch wenn beim Schließen ein Fehler gemeldet wird. Rufe fclose nicht erneut mit demselben Zeiger auf und verwende ihn nicht für weitere Ein- oder Ausgaben. Schließe Dateien zeitnah: Die verfügbaren Datenströme sind begrenzt, und C garantiert die Unterstützung von mindestens acht offenen Datenströmen einschließlich der drei Standarddatenströme.
Pfade vom laufenden Programm aus auflösen
Ein relativer Pfad wie "data/input.txt" beginnt beim aktuellen Arbeitsverzeichnis, nicht beim Ordner der Quelldatei. Wird das Programm an einer anderen Stelle gestartet, bezeichnet dieser Pfad etwas anderes. Schrägstriche funktionieren auch mit der C-Bibliothek von Microsoft. Ein umgekehrter Schrägstrich in einer C-Zeichenkette muss verdoppelt werden, wie in "C:\\data\\in.txt"; andernfalls kann eine Escape-Sequenz wie \n die Zeichenkette verändern.
Verwende für Namen, die auf der Kommandozeile übergeben werden, int main(int argc, char *argv[]). Bei einem Kopierbefehl mit zwei Namen muss argc gleich 3 sein, argv[1] ist der Eingabename und argv[2] der Ausgabename. Öffne zuerst die Eingabe, damit bei einem falsch geschriebenen Eingabenamen keine Ausgabe erstellt oder gelöscht wird. Schlägt das Öffnen der Ausgabe fehl, schließe die bereits geöffnete Eingabe. Verwende unterschiedliche Ein- und Ausgabedateien: Das zuerst erfolgte Öffnen der Eingabe schützt sie nicht, wenn der Ausgabename dieselbe Datei bezeichnet.
Eine normale Rückkehr aus main oder exit schließt Datenströme, aber ausdrückliche Schließvorgänge ermöglichen dem Programm, verzögerte Ausgabefehler zu erkennen. Bei einem Absturz können gepufferte Daten verloren gehen; abort bietet nicht die Garantie zur Ressourcenfreigabe, die bei normalem Beenden gilt.
Wichtige Erkenntnisse
- Das Öffnen mit
"w"löscht den Inhalt einer vorhandenen Datei sofort. - Prüfe
fopenaufNULL, bevor du den Datenstrom verwendest. - Relative Pfade beginnen beim aktuellen Arbeitsverzeichnis.
- Schließe jeden geöffneten Datenstrom einmal und verwende ihn nach
fclosenie erneut. - Öffne beim Kopieren die Eingabe vor der Ausgabe und verwende unterschiedliche Dateien.
- Exklusive Schreibmodi aus C11 schlagen fehl, wenn die Zieldatei existiert.
1.3 stdin, stdout und stderr mit Umleitungen und Pipes verwenden
Ein C-Programm startet mit drei offenen Datenströmen: Standardeingabe stdin, Standardausgabe stdout und Standardfehlerausgabe stderr. Sie sind normalerweise mit dem Terminal verbunden, aber die Shell kann sie mit Dateien oder anderen Programmen verbinden. Du kannst sie direkt an Datenstromfunktionen übergeben.
Ergebnisse von Diagnosemeldungen trennen
printf schreibt nach stdout; scanf liest aus stdin. getchar() liest ein Zeichen aus stdin, und putchar(c) schreibt eines nach stdout. puts(s) schreibt eine Zeichenkette nach stdout und fügt einen Zeilenumbruch hinzu. Mit ihren Datenstromformen kann derselbe Code mit einer Datei arbeiten:
void print_total(FILE *out, int total)
{
fprintf(out, "Total: %d\n", total);
}
Der Aufruf print_total(stdout, 12) gibt ein Ergebnis aus; die Übergabe eines Zeigers auf eine geöffnete Datei leitet dasselbe Format in eine Datei. In produktivem Code sollte die Hilfsfunktion einen fehlgeschlagenen Schreibvorgang an ihren Aufrufer melden.
Schreibe Diagnosemeldungen mit fprintf(stderr, ...). Ein datenverarbeitendes Programm kann dann eine reine Ergebnisdatei erzeugen und Fehler getrennt erklären. Ein auf stdout ausgegebener Fehler kann die Daten verfälschen, die ein anderes Programm erwartet.
Die Shell über Verbindungen entscheiden lassen
In einer Unix-artigen Shell trennt dieser Befehl alle drei Datenströme:
./sortnums < numbers.txt > sorted.txt 2> errors.txt
< liefert die Eingabe, > erstellt oder ersetzt die Ausgabe, >> hängt Ausgabe an und 2> leitet Fehler um. Die Umleitungsoperatoren funktionieren auch in der Windows-Eingabeaufforderung, wo der Programmname gewöhnlich als sortnums.exe geschrieben wird. Eine Pipe, wie in generate | sortnums, sendet die Standardausgabe des ersten Programms an die Standardeingabe des zweiten. Standardmäßig überträgt sie stderr nicht.
Ein Filterprogramm kann einen optionalen Eingabenamen annehmen und andernfalls stdin lesen. Achte auf den Besitz: Schließe einen Datenstrom, den deine Funktion geöffnet hat, überlasse einen geliehenen Datenstrom aber seinem Besitzer. Bei einem einfachen Kommandozeilenfilter erkennt if (in != stdin) die Datei, die der Filter selbst geöffnet hat.
Aufforderungen vor dem Warten auf Eingabe ausgeben
Bei einem Terminal ist stdout häufig zeilengepuffert. Bei einer umgeleiteten Datei oder Pipe ist die Ausgabe häufig vollständig gepuffert. stderr ist nicht vollständig gepuffert. Das erklärt, warum sich die Ausgabereihenfolge ändern kann, wenn beide Datenströme an dasselbe Ziel gesendet werden.
Nach printf("Saving...") kann der Text im Puffer bleiben, weil kein Zeilenumbruch vorhanden ist. Verwende fflush(stdout) und prüfe auf EOF, wenn der Text vor dem nächsten Vorgang erscheinen muss. Das Leeren des Puffers sendet ausstehende Ausgaben; es liest oder verwirft keine Eingaben. Standard-C definiert fflush(stdin) nicht als Vorgang zum Leeren der Eingabe. Um eine Zeile zu verwerfen, lies Zeichen bis zu '\n' oder EOF.
Gibt ein Programm zwei Zeilen auf stdout und dazwischen eine Warnung auf stderr aus, zeigt ein Terminal gewöhnlich die Programmreihenfolge. Mit > out.txt 2>&1 kann die Warnung vor der gepufferten Standardausgabe in der Datei ankommen. Verwende die Anzeigereihenfolge gemischter Datenströme nicht als Nachweis der Ausführungsreihenfolge.
Skripten ein brauchbares Ergebnis zurückgeben
Gib EXIT_SUCCESS oder 0 zurück, wenn die gesamte Aufgabe gelingt, und EXIT_FAILURE, wenn sie fehlschlägt. Beide Makros sind in <stdlib.h> deklariert. Ein Shell-Befehl wie convert in.csv out.txt && echo done führt den zweiten Befehl nur nach Erfolg aus. Ein wahrheitsgemäßer Rückgabestatus ist ebenso wichtig wie eine hilfreiche Diagnosemeldung.
Wichtige Erkenntnisse
- Verwende
stdin,stdoutundstderr, ohne sie zu öffnen. - Sende Programmergebnisse an
stdoutund Diagnosemeldungen anstderr. - Eine Umleitung ändert Verbindungen; eine Pipe überträgt die Standardausgabe an die Standardeingabe.
- Leere den Ausgabepuffer, wenn eine Aufforderung sofort sichtbar sein muss.
- Gib einen Rückgabestatus für einen Fehler zurück, wenn die angeforderte Aufgabe fehlschlägt.
2Textdateien lesen und schreiben
Text kann auf drei nützlichen Ebenen verarbeitet werden: als einzelne Zeichen, formatierte Werte und ganze Zeilen. Die Wahl beeinflusst, was das Programm auf Zulässigkeit prüfen kann und wie es auf fehlerhafte Eingaben reagiert. Eine Zeichenschleife muss Bytes vom Dateiende unterscheiden; beim formatierten Lesen muss die Zuweisungsanzahl geprüft werden; beim zeilenweisen Lesen müssen Kapazität und unvollständige Datensätze behandelt werden. Durchgerechnete Beispiele zeigen, wie du die tatsächlich gelesenen Daten erhältst und das spätere Parsen und Melden unter Kontrolle behältst.
2.1 Zeichenweise lesen und schreiben
Die Zeichenein- und -ausgabe ist nützlich, wenn jedes Zeichen den nächsten Schritt bestimmt: beim Zählen von Trennzeichen, Ändern der Groß- und Kleinschreibung oder Erkennen eines einfachen Textformats. fgetc liest ein Zeichen und fputc schreibt eines. Beide sind in <stdio.h> deklariert.
Den Rückgabewert in einem int speichern
fgetc(fp) gibt das Zeichen als in int umgewandelten unsigned char zurück oder die negative Konstante EOF, wenn kein Zeichen verfügbar ist, weil die Eingabe endete oder ein Lesevorgang fehlschlug. Auf den üblichen Systemen mit 8-Bit-Bytes reichen gültige Bytewerte von 0 bis 255. Ein int hält diese Werte von EOF unterscheidbar.
int c;
while ((c = fgetc(in)) != EOF) {
if (fputc(c, out) == EOF) {
/* Report a write failure and clean up. */
break;
}
}
/* Check ferror(in) before declaring the input complete. */
Bei einem einfachen char kann diese Unterscheidung verloren gehen. Bei vorzeichenbehaftetem char kann das Byte 0xFF zu -1 werden und wie ein Dateiende aussehen. Bei vorzeichenlosem char wird der negative Wert EOF in ein Byte umgewandelt und kann beim Vergleich niemals gleich EOF sein.
Behalte beide Klammern um die Zuweisung bei. In c = fgetc(in) != EOF findet zuerst der Vergleich statt, sodass c statt des Zeichens 1 oder 0 erhält. getc übernimmt dieselbe Leseaufgabe und kann ein Makro sein; fputc gibt das geschriebene Zeichen oder EOF zurück.
Erfolgreiche Lesevorgänge statt Versuche zählen
Die Schleifenbedingung muss den Lesevorgang ausführen. feof beschreibt einen früheren Versuch, der das Ende erreicht hat; es kann den nächsten Versuch nicht vorhersagen. Eine Schleife, die !feof(in) prüft und dann unbedingt zählt, fügt am Ende einen zusätzlichen Durchlauf hinzu.
Beim Text "hi\nyo\n" ergeben sechs erfolgreiche Lesevorgänge sechs Zeichen und zwei Zeilenumbruchzeichen. Das alleinige Zählen von Zeilenumbruchzeichen funktioniert für Zeilen, die mit einem Zeilenumbruch enden. Der Text "red\ngreen\nblue" hat zwei Zeilenumbrüche, aber drei Zeilen, weil das letzte "blue" ebenfalls eine Zeile ist.
Um eine letzte Zeile ohne Zeilenumbruch mitzuzählen, merke dir auch das letzte Zeichen:
size_t lines = 0;
int c, last = '\n';
while ((c = fgetc(in)) != EOF) {
if (c == '\n') ++lines;
last = c;
}
if (last != '\n') ++lines;
Der Anfangswert '\n' für last bewirkt, dass eine leere Datei als null Zeilen gezählt wird. Ein Lesefehler benötigt weiterhin einen gesonderten Fehlerpfad; eine Teilzählung ist kein vollständiges Ergebnis.
Umwandeln oder vorausschauen
toupper aus <ctype.h> kann Buchstaben vor dem Schreiben umwandeln. Ein erfolgreiches fgetc-Ergebnis liegt bereits im zulässigen Eingabebereich für toupper, sodass fputc(toupper(c), out) innerhalb der Schleife sicher ist. Beliebige vorzeichenbehaftete char-Werte müssen vor Zeichenklassifikationsfunktionen in unsigned char umgewandelt werden.
ungetc(c, in) stellt ein Zeichen zurück, sodass der nächste Lesevorgang es erneut liefert. Das Zurückstellen eines Zeichens ist garantiert; prüfe auf EOF, falls das Zurückstellen fehlschlägt. So kann ein Parser das nächste Zeichen prüfen, ohne es zu verlieren. Ein Positionierungsvorgang verwirft zurückgestellte Zeichen.
Die Zählung beschreibt, was der gewählte Datenstrommodus liefert. Unter Windows zählt der Textmodus ein CR-LF-Paar als ein '\n'; der Binärmodus macht beide Bytes sichtbar. Eine Zeichenzählung über einen Textstrom muss daher nicht der gespeicherten Byteanzahl entsprechen.
Wichtige Erkenntnisse
- Speichere
fgetc-Ergebnisse inint, damit Bytewerte vonEOFunterscheidbar bleiben. - Prüfe jedes Leseergebnis, bevor du es verarbeitest oder zählst.
- Zähle einen nicht leeren letzten Teil ohne Zeilenumbruch als Zeile.
- Das Zurückstellen eines Zeichens mit
ungetcist garantiert; eine Positionierung verwirft es.
2.2 Formatierten Text mit fprintf schreiben und mit fscanf lesen
Formatierte Ein- und Ausgabe wandelt zwischen C-Werten und lesbarem Text um. fprintf schreibt in einem von dir gewählten Format; fscanf liest Werte, die zu einem Format passen. Ihre Konvertierungsspezifikationen sehen ähnlich aus, aber die Eingabe benötigt Zeiger auf die richtigen Zieltypen.
Lesbare Werte schreiben
Verwende %d für ein int, %ld für ein long, %zu für ein size_t, %s für eine Zeichenkette und %c für ein Zeichen. Bei der Ausgabe druckt %f einen double; ein float-Argument wird zu double erweitert. %% gibt ein Prozentzeichen aus.
int result = fprintf(fp, "%-8s%6.2f\n", "Oslo", 3.14159);
if (result < 0) {
/* Handle a write failure. */
}
%-8s richtet "Oslo" in acht Spalten linksbündig aus; %6.2f richtet "3.14" in sechs Spalten rechtsbündig aus. Die resultierende Zeile lautet "Oslo 3.14\n", und der Rückgabewert beträgt 15 Zeichen. Die Feldbreite ist ein Mindestwert, keine Begrenzung der Ausgabelänge. Die Genauigkeit verändert auch die gespeicherte Information: %.2f schreibt 3.14159 als "3.14", sodass sich die zusätzlichen Ziffern aus der Datei nicht wiederherstellen lassen.
Ein einfaches CSV-Schreibprogramm kann "Lima,2024,19.5" mit fester Reihenfolge und Kommas zwischen den Feldern ausgeben. Text mit Kommas, Anführungszeichen oder Zeilenumbrüchen benötigt eine Maskierung von CSV-Feldern. Schließe ein solches Feld in doppelte Anführungszeichen ein und verdopple jedes Anführungszeichen darin. Ein einfaches Schreibprogramm mit %s ist nur geeignet, wenn Textfelder diese Zeichen nicht enthalten.
In ein Array begrenzter Größe formatieren
sprintf hat kein Argument für die Größe des Ziels. Das Formatieren von "007.jpg" benötigt einschließlich des abschließenden Nullzeichens acht Byte. Verwende vorzugsweise snprintf:
char name[32];
int n = snprintf(name, sizeof name, "%03d.jpg", 7);
if (n < 0 || (size_t)n >= sizeof name) {
/* Formatting failed or the full result did not fit. */
}
Der nicht negative Rückgabewert ist die Länge, die der vollständige Text ohne das abschließende Nullzeichen benötigen würde. Bereits die Gleichheit mit der Puffergröße bedeutet, dass der Text abgeschnitten wurde. Prüfe auf einen negativen Wert, bevor du das Ergebnis in size_t umwandelst.
Die erwartete Anzahl von Werten lesen
char name[20];
int age;
int matched = fscanf(fp, "%19s %d", name, &age);
Dieser Aufruf ist nur vollständig erfolgreich, wenn er 2 zurückgibt. Die Breite 19 lässt ein Byte für das abschließende Nullzeichen frei. Der Arrayname liefert seine Adresse; die Ganzzahl benötigt &age. Für die Eingabe benötigt %f einen float *, während %lf einen double * benötigt. Eine Abweichung führt zu undefiniertem Verhalten und ist keine harmlose Umwandlung.
Die meisten Konvertierungen überspringen vorangestellte Leerraumzeichen einschließlich Zeilenumbrüchen. fscanf erzwingt daher nicht einen Datensatz pro Zeile, und %s kann "New York" nicht als einen einzelnen Wert lesen. Eine Zeichenmenge wie %19[^,] liest bis zu einem Komma; für Datensätze ist das Lesen ganzer Zeilen oft klarer.
Bei "10 abc 20" speichert %d zunächst 10 und gibt 1 zurück. Der nächste Aufruf gibt 0 zurück, weil "abc" nicht passt; dieser Text bleibt ungelesen. Die Prüfung auf != EOF wiederholt den Fehler endlos. Prüfe auf == 1 und behandle dann ungültige Daten getrennt vom Eingabeende oder einem Datenstromfehler. Ein Konvertierungsergebnis ist eine Anzahl zugewiesener Werte, kein Wahrheitswert für die gesamte Datei.
Wichtige Erkenntnisse
- Stimme jede Konvertierungsspezifikation auf den Argumenttyp ab.
- Begrenze
%sbei der Eingabe auf die Arraygröße minus eins. - Vergleiche den Rückgabewert von
fscanfmit der genau erwarteten Zuweisungsanzahl. snprintfgibt die erforderliche vollständige Länge zurück; ein Rückgabewert mindestens in Höhe der Kapazität bedeutet, dass der Text abgeschnitten wurde.- CSV-Textfelder müssen maskiert werden, wenn sie Kommas, Anführungszeichen oder Zeilenumbrüche enthalten.
- Formatierte Gleitkommaausgaben können Genauigkeit verlieren.
2.3 Zeilen mit fgets sicher lesen
Wenn du vor dem Parsen eine vollständige Zeile liest, bleiben die Textdatensätze getrennt. Ist ein Datensatz ungültig, kannst du seine Zeilennummer melden und mit dem nächsten fortfahren. fgets begrenzt die geschriebene Datenmenge, aber du musst weiterhin erhaltene Zeilenumbrüche und Zeilen behandeln, die größer als das Array sind.
In einen Puffer bekannter Kapazität lesen
char line[256];
while (fgets(line, sizeof line, fp) != NULL) {
/* Process this successful read. */
}
if (ferror(fp)) {
/* Report an interrupted read. */
}
fgets liest höchstens ein Zeichen weniger als die Kapazität, hält nach einem Zeilenumbruch an, falls es auf einen trifft, und fügt ein abschließendes Nullzeichen hinzu. Bei Erfolg gibt die Funktion den Puffer zurück. Ein NULL-Ergebnis bedeutet entweder, dass am Dateiende keine Zeichen verfügbar waren, oder dass ein Fehler aufgetreten ist; verarbeite den Puffer nach einem Lesefehler nicht.
sizeof line liefert in diesem Gültigkeitsbereich die Arraykapazität. In einer Hilfsfunktion mit dem Parameter char *line ist es die Größe des Zeigers. Übergib die tatsächliche Kapazität getrennt. Die alte Funktion gets kann die Eingabe nicht begrenzen und wurde in C11 entfernt.
Teile langer Zeilen erkennen
Bei char buf[8] kommt "temperature\n" als "tempera" und danach als "ture\n" an. Der erste Aufruf verwendet sieben Byte für Zeichen und eines für das abschließende Nullzeichen. Ein erfolgreicher Lesevorgang ohne Zeilenumbruch kann einen Teil einer langen Zeile oder den letzten nicht mit einem Zeilenumbruch abgeschlossenen Teil am Dateiende liefern.
Wähle ein Vorgehen: Füge Teile zu einem größeren Datensatz zusammen, verarbeite sie als fortlaufende Folge oder weise den Datensatz zurück und verwirf alle seine übrigen Teile. Prüfe bei einer Größenbegrenzung das nächste Zeichen, bevor du einen genau gefüllten Puffer zurückweist. Ein anschließender Zeilenumbruch vervollständigt den Datensatz; EOF ohne Datenstromfehler schließt eine letzte Zeile ohne Zeilenumbruch ab; ein anderes Zeichen bedeutet, dass weiterer Inhalt folgt. Stelle ein Zeichen nur zurück, wenn späterer Code es erneut lesen muss.
Nur ein tatsächlich gefundenes Zeilenende entfernen
Entferne die Zeilenenden, nachdem du festgestellt hast, ob der Datensatz vollständig ist:
line[strcspn(line, "\r\n")] = '\0';
strcspn aus <string.h> findet das erste aufgeführte Zeichen oder das vorhandene abschließende Nullzeichen, wenn keines der beiden vorkommt. Das behandelt sowohl ein erhaltenes CR LF als auch LF. Das bedingungslose Löschen von line[strlen(line) - 1] kann echte Daten aus einer letzten Zeile ohne Zeilenumbruch entfernen und ist bei einer leeren Zeichenkette ungültig.
fputs(line, out) schreibt die Zeichenkette, ohne einen Zeilenumbruch hinzuzufügen; puts(line) schreibt nach stdout und fügt einen hinzu. Beim Kopieren von Text kann jeder erfolgreich mit fgets gelesene Teil an fputs übergeben und auf Fehler geprüft werden, auch wenn sich eine lange Zeile über mehrere Teile erstreckt. Verwende für die exakten Originalbytes stattdessen die blockweise binäre Ein- und Ausgabe.
Getrennt parsen und auf Zulässigkeit prüfen
sscanf(line, "%d %9s", &qty, item) gibt für "7 apples" den Wert 2 und für "seven apples" den Wert 0 zurück. Bei einem fehlgeschlagenen Parsen bleibt kein fehlerhaftes Token im Eingabedatenstrom stecken, weil die Zeile bereits gelesen wurde. Numerische Konvertierungen mit sscanf setzen weiterhin darstellbare Eingaben voraus; verwende strtol für eine strenge Wertebereichsprüfung.
Setze für strtol errno zurück, übergib einen Endzeiger und prüfe, ob keine Ziffern gefunden wurden, ERANGE aufgetreten ist oder unerwarteter Text folgt. Bei "42abc" ist das Ergebnis 42 nur ein numerisches Präfix. Erlaube bei einem Ganzzahlfeld die gewählten nachfolgenden Leerraumzeichen und verlange danach das Ende der Zeichenkette. Prüfe den Wertebereich des Zieltyps, bevor du einen gültigen long zu int verengst.
Wichtige Erkenntnisse
fgetsbehält einen Zeilenumbruch bei, wenn er hineinpasst, und reserviert bei Erfolg immer Platz für ein abschließendes Nullzeichen.- Ein voller Puffer ohne Zeilenumbruch muss auf Vollständigkeit geprüft werden.
- Übergib Kapazitäten an zeigerbasierte Hilfsfunktionen ausdrücklich.
- Verwende
strcspn, um nur ein vorhandenes Zeilenende zu entfernen. - Lies jeden Datensatz zuerst und parse und prüfe ihn anschließend.
- Prüfe bei
strtolauf Ziffern, Wertebereichsfehler und nachfolgende Daten.
3Daten in Binärdateien speichern und daraus laden
Binärdateien übertragen die Bytes von Objekten, ohne sie in lesbaren Text umzuwandeln. Elementgrößen und -anzahlen bestimmen, wie vollständige Arrays, Datensätze und unvollständige letzte Blöcke behandelt werden. Eine Datei, die zwischen Rechnern ausgetauscht wird, benötigt außerdem eine ausdrücklich festgelegte Darstellung: Feldbreiten, Byte-Reihenfolge und Aufbau. Das Lesen einer Signatur ist nur der Anfang der Prüfung dieser Darstellung; Längen und Anzahlen müssen geprüft werden, bevor das Programm den restlichen Daten vertraut oder Speicherplatz dafür reserviert.
3.1 Binärdaten mit fread lesen und mit fwrite schreiben
Die binäre Ein- und Ausgabe überträgt die Bytes von Objekten, ohne sie in lesbare Zahlen oder Wörter umzuwandeln. fread und fwrite erhalten eine Adresse, eine Elementgröße, eine Elementanzahl und einen Datenstrom. Ihre Rückgabewerte zählen vollständige Elemente. Die Wahl der Elementgröße bestimmt daher, was du aus dem Ergebnis ablesen kannst.
Arrays schreiben und lesen
int a[5] = {1, 2, 3, 4, 5};
if (fwrite(a, sizeof a[0], 5, out) != 5) {
/* Handle a write failure. */
}
Auf einem System mit einem vier Byte großen int schreibt dies 20 Byte und gibt 5 zurück. Durch Lesen in ein anderes Array mit fünf Elementen und derselben Darstellung lassen sich die Werte wiederherstellen. Verwende "wb" und "rb", damit Textumwandlungen die Bytes nicht verändern können.
Übergib für ein einzelnes Objekt seine Adresse: fwrite(&rec, sizeof rec, 1, out). Für Objekte, auf die über einen Zeiger p zugegriffen wird, verwende fwrite(p, sizeof *p, count, out). sizeof p misst den Zeiger, und &p zeigt auf die Zeigervariable selbst. Keines von beiden beschreibt die beabsichtigte Datenübertragung. Sind Elementgröße oder -anzahl null, geben beide Funktionen null zurück, ohne Daten zu übertragen.
Genau die gelesenen Bytes kopieren
unsigned char buf[512];
size_t n;
while ((n = fread(buf, 1, sizeof buf, in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
/* Handle failure and stop copying. */
break;
}
}
Bei einer Elementgröße von 1 ist n eine Byteanzahl. Eine Eingabe von 1.300 Byte liefert 512, 512 und 276 Byte, dann null. Der letzte Schreibvorgang muss 276 verwenden, nicht die Kapazität 512. Wird der ganze Puffer geschrieben, werden 236 Byte hinzugefügt, die von früheren Lesevorgängen übrig sind.
Mit fread(buf, 512, 1, in) liefert dieselbe Eingabe 1, 1 und dann 0. Die letzten 276 Byte bilden nur ein unvollständiges Element; sie werden nicht gezählt, und der Wert dieses unvollständigen Elements ist unbestimmt. Wähle Bytes als Elemente, wenn ein unvollständiger letzter Block nützlich ist. Wähle Elemente in Datensatzgröße, wenn unvollständige Datensätze zurückgewiesen werden müssen.
Ein fread mit zu wenigen Elementen kann das Dateiende oder einen Fehler bedeuten. Prüfe nach dem Kopieren ferror und beim Schließen der Ausgabe fclose: Ein in den Puffer übernommener Schreibvorgang kann später fehlschlagen. Ein unvollständiger Dateikopf oder Datensatz darf niemals als vollständiges Objekt behandelt werden.
Signaturen und gespeicherte Objekte prüfen
Verwende unsigned char für rohe Bytes; auf Systemen mit 8-Bit-Bytes enthält der Typ Werte von 0 bis 255 ohne negative Werte. JPEG-Daten beginnen mit 0xFF 0xD8. Bei Fotos von Kameras und Smartphones folgt oft eine APPn-Markierung, sodass das Suchmuster FF D8 FF E0 bis FF D8 FF EF entsteht. Dieses Muster eignet sich, um mögliche Treffer zu finden; prüfe das Format, bevor du ihm vertraust. Bei manchen JPEGs folgt ein anderes Segment.
Eine Struktur mit char name[20] und einem int score belegt gewöhnlich 24 Byte. Beim rohen Schreiben werden alle gespeicherten Bytes einschließlich der Füllbytes ausgegeben. Eine Zeigerkomponente speichert eine Adresse, nicht die Zeichenkette, auf die sie zeigt. Verwende ein eingebettetes Array oder schreibe die Länge und die Zeichen der Zeichenkette getrennt.
Lesbarer Text kann mehr Platz benötigen: "1000000\n" belegt unter Linux acht Byte und im Windows-Textmodus neun. Eine ausdrücklich kodierte 32-Bit-Ganzzahl benötigt vier Bytes mit jeweils acht Bit. Die rohe Speicherung von Objekten ist kompakt, bindet die Datei aber an die Darstellung des erzeugenden Systems.
Wichtige Erkenntnisse
freadundfwritegeben die Anzahl vollständiger Elemente zurück, nicht immer eine Byteanzahl.- Verwende die Elementgröße 1, wenn ein unvollständiger letzter Block relevant ist.
- Schreibe die gelesene Anzahl, nicht die gesamte Pufferkapazität.
- Eine rohe Zeigerkomponente speichert eine Adresse, nicht die Daten, auf die sie zeigt.
- Prüfe unvollständige Übertragungen und das Schließen der Ausgabe.
- Verwende Signaturen, um mögliche Treffer zu erkennen, und prüfe anschließend die Datei.
3.2 Rechnerübergreifend verwendbare Binärformate entwerfen
Ein Binärstrom erhält Bytes, aber zwei Rechner können dieselben Bytes unterschiedlich interpretieren. Ein portables Format legt fest, was jedes Byte bedeutet, statt den vom Compiler gewählten Aufbau eines Objekts zu übernehmen.
Größen, Reihenfolge und Aufbau festlegen
Drei Merkmale machen das rohe Speichern eines Objekts rechnerabhängig:
| Merkmal | Was sich unterscheiden kann |
|---|---|
| Typgröße | int kann zwei oder vier Byte groß sein; long unterscheidet sich häufig zwischen 64-Bit-Windows und Linux |
| Byte-Reihenfolge | Das niederwertigste oder höchstwertige Byte kann zuerst stehen |
| Strukturaufbau | Füllbytes und Versätze der Komponenten können sich ändern |
Bei Little-Endian wird das niederwertigste Byte zuerst gespeichert. Bei Big-Endian wird das höchstwertige Byte zuerst gespeichert. Der Wert 258, hexadezimal 0x00000102, lautet in Little-Endian-Reihenfolge 02 01 00 00 und in Big-Endian-Reihenfolge 00 00 01 02.
Ein gängiger Aufbau einer Struktur mit einem char gefolgt von einem int belegt acht Byte: ein Byte für das Zeichen, drei Füllbytes und vier für die Ganzzahl. Die Werte der Füllbytes sind nicht spezifiziert. Typen mit exakter Bitbreite aus <stdint.h>, etwa uint32_t, legen die Bitbreite fest, wo sie verfügbar sind; sie legen weder die Byte-Reihenfolge fest noch entfernen sie Füllbytes. Gängige Dateiformatbeispiele setzen 8-Bit-Bytes und die Verfügbarkeit dieser Typen voraus.
Felder ausdrücklich dekodieren
Lies vier Bytes vollständig, bevor du sie interpretierst. Für ein Feld in Little-Endian-Reihenfolge:
uint32_t v = (uint32_t)b[0]
| (uint32_t)b[1] << 8
| (uint32_t)b[2] << 16
| (uint32_t)b[3] << 24;
Hier ist b ein Array vorzeichenloser Bytes. Jede explizite Typumwandlung findet vor der Bitverschiebung statt, sodass die Arithmetik die erforderliche vorzeichenlose Bitbreite hat. Zum Kodieren extrahiere die niederwertigen acht Bit, dann die nächsten acht und so weiter. Prüfe jeden Ausgabevorgang. Würde v direkt mit fwrite geschrieben, gelangte die Byte-Reihenfolge des Rechners wieder in das Format.
Ein vollständiges kleines Format definieren
Ein Format für Punktestände kann "SCR1" als vier Byte große Signatur, eine vier Byte große Anzahl in Little-Endian-Reihenfolge und Datensätze aus 20 Byte für den Namen und vier Byte für den Punktestand in Little-Endian-Reihenfolge verwenden. Die Signatur bezeichnet auch die Version. Der Dateikopf ist acht Byte groß; jeder Datensatz ist genau 24 Byte groß.
Fünf Datensätze belegen 128 Byte. Der Datensatz mit Index 3 beginnt bei Byte 80: Überspringe acht Byte des Dateikopfs und drei vollständige Datensätze. Lege bei einem echten Format die Kodierung des Namensfelds sowie die Regeln für seinen Abschluss oder seine Auffüllung fest; ein festes 20 Byte großes Namensfeld ist nicht automatisch eine C-Zeichenkette.
Prüfe die vollständige Länge und die Signatur eines Dateikopfs, bevor du seine Felder verwendest. Prüfe dann Anzahlen und Längen anhand sinnvoller Höchstwerte und der verfügbaren Daten, bevor du Speicher reservierst. Eine plausible Anzahl genügt nicht, wenn spätere Datensätze fehlen.
Die tatsächliche Formatstruktur lesen
Einige PCM-WAV-Dateien haben einen einfachen 44 Byte großen Dateikopf, bei dem die Abtastrate in den Bytes 24 bis 27 steht. Die Bytes 44 AC 00 00 ergeben beim Dekodieren 44.100 Abtastwerte pro Sekunde. Andere WAV-Dateien enthalten zusätzliche Blöcke, sodass ein allgemeines Leseprogramm anhand der Blockkennungen und -größen den fmt -Block finden muss. Wer für jede Datei denselben festen Aufbau des Dateikopfs annimmt, kann ein anderes Feld fälschlich als Abtastrate lesen.
Text wie CSV vermeidet Probleme mit Ganzzahlgrößen, Byte-Reihenfolge und Füllbytes in Strukturen und lässt sich leicht prüfen. Er benötigt dennoch einen definierten Feldaufbau, Maskierungsregeln und eine Prüfung beim Parsen. Wähle ihn, wenn Lesbarkeit und Austausch wichtiger sind als eine kompakte binäre Darstellung.
Wichtige Erkenntnisse
- Ein portables Binärformat legt Feldbreiten, Byte-Reihenfolge und Versätze fest.
- Ganzzahltypen mit exakter Bitbreite allein machen eine roh gespeicherte Struktur nicht portabel.
- Wandle Bytes vor der Bitverschiebung in einen vorzeichenlosen Typ mit der gewünschten Bitbreite um.
- Prüfe den gesamten Dateikopf und begrenze Anzahlen, bevor du ihnen vertraust.
- Folge der Blockstruktur, wenn ein Format zusätzliche Blöcke erlaubt.
- Ein Textfeld fester Breite benötigt ausdrücklich festgelegte Regeln für Kodierung und Länge.
4Beliebige Positionen in einer Datei ansteuern
Dateipositionen ermöglichen den direkten Zugriff, wenn die Positionen von Datensätzen bekannt sind. Um diese Positionen zu berechnen, musst du Dateiköpfe, Datensatzbreiten und Indizes ab null beachten. Für Positionen in Textströmen gelten strengere Regeln, weil Umwandlungen ihre Bedeutung ändern können. Wenn ein Datenstrom sowohl das Lesen als auch das Schreiben unterstützt, bestimmt die Positionierung außerdem, wann ein Wechsel zwischen beiden zulässig ist. Die Aktualisierung eines Datensatzes verbindet daher eine Positionsberechnung mit einer geprüften Abfolge, die die umgebenden Daten erhält.
4.1 Die Dateiposition mit ftell, fseek und rewind ermitteln und setzen
Sequentielle Lesevorgänge erfassen Daten der Reihe nach. Direktzugriff ermöglicht es einem Programm, zu einer bekannten Stelle zu springen, etwa zu einem Datensatz in einer großen Datei. Die Positionierung funktioniert nur, wenn der zugrunde liegende Datenstrom sie unterstützt, und die Bedeutung einer Position unterscheidet sich zwischen Binär- und Textströmen.
Position ermitteln und setzen
ftell(fp) gibt eine Position als long oder bei einem Fehler -1L zurück. Bei einer Binärdatei zählt eine Position die Bytes vom Anfang an. fseek(fp, offset, whence) verschiebt die Position relativ zu einem gewählten Bezugspunkt und gibt bei Erfolg 0, bei einem Fehler einen Wert ungleich null zurück.
| Bezugspunkt | Bedeutung des Versatzes |
|---|---|
SEEK_SET | Vom Anfang aus |
SEEK_CUR | Von der aktuellen Position aus |
SEEK_END | Vom Ende aus |
Zum Beispiel fordert fseek(fp, -4L, SEEK_END) die letzten vier Bytes einer Binärdatei auf einem System an, das Positionierungen relativ zum Ende unterstützt. Prüfe das Ergebnis vor dem Lesen. Eine Anforderung für eine Pipe oder ein Terminal oder eine Position vor dem Anfang einer gewöhnlichen Datei kann fehlschlagen.
Die Position eines Datensatzes berechnen
Bei binären Datensätzen fester Größe ohne Dateikopf ergibt der mit der Datensatzbreite multiplizierte Index ab null den Anfangsversatz. Bei 24 Byte großen Datensätzen beginnt Index 3 bei Byte 72. Nachdem dieser Datensatz vollständig gelesen wurde, steht die Position bei 96.
long offset = 3L * (long)sizeof(struct record);
if (fseek(fp, offset, SEEK_SET) != 0) {
/* Handle the failed move before reading. */
}
Dieses Beispiel setzt voraus, dass die Datei mit demselben Strukturaufbau geschrieben wurde. Ein festgelegtes portables Format verwendet stattdessen seine definierte Datensatzbreite. Berücksichtige gegebenenfalls die Bytes des Dateikopfs. Der dritte Datensatz, ab 1 gezählt, hat den Index 2, nicht 3. Prüfe bei großen Indizes die Multiplikation und stelle vor dem Positionieren sicher, dass das Ergebnis in long darstellbar ist.
Auf gängigen Systemen lässt sich eine Binärdatei durch Positionieren am Ende und mit ftell ausmessen: Eine 120 Byte große Datei mit 24 Byte großen Datensätzen enthält fünf Datensätze. Prüfe beide Aufrufe und stelle danach die Position wieder her. Standard-C verlangt nicht für jeden Binärstrom ein sinnvolles SEEK_END, daher hängt die Methode von der Unterstützung durch den jeweiligen Datenstrom ab.
Positionen in Textströmen ohne Berechnung speichern
Der Textmodus kann Zeilenenden umwandeln. Eine zurückgegebene Position in einem Textstrom dient dazu, zu einer Stelle zurückzukehren, und ist keine Anzahl zum Addieren oder Subtrahieren. Zum Positionieren mit einem Wert ungleich null wird ein zuvor von ftell für dieselbe Datei zurückgegebener Wert mit SEEK_SET verwendet. Ein Versatz von null ist mit jedem Bezugspunkt erlaubt.
rewind(fp) kehrt zum Anfang zurück und setzt sowohl den Dateiende- als auch den Fehlerindikator zurück. Die Funktion gibt kein Ergebnis zurück. Verwende daher das geprüfte fseek(fp, 0L, SEEK_SET), wenn die erfolgreiche Positionierung bestätigt werden muss. Ein erfolgreiches fseek setzt den Dateiendeindikator zurück und verwirft mit ungetc zurückgestellte Zeichen, lässt aber einen vorhandenen Fehlerindikator gesetzt.
Eine Positionsangabe speichern
fgetpos(fp, &pos) speichert eine Position in fpos_t; fsetpos(fp, &pos) stellt sie wieder her. Prüfe bei beiden Funktionen, ob ein Wert ungleich null einen Fehler meldet. Behandle fpos_t als undurchsichtige Positionsangabe, mit der nicht gerechnet wird. Sie kann Positionsinformationen enthalten, die über eine einfache Byteanzahl hinausgehen.
fseek und ftell verwenden long, das unter Windows häufig 32 Bit groß ist und die darstellbaren Positionen auf ungefähr 2 GiB begrenzt. Eine Positionsangabe vermeidet, die Schnittstelle an long zu binden, aber die unterstützte Dateigröße hängt weiterhin von Implementierung und Konfiguration ab.
Wichtige Erkenntnisse
- Prüfe
fseekauf einen Wert ungleich null undftellauf-1L. - Der Versatz eines Datensatzes umfasst den Dateikopf und alle vorherigen Datensätze.
- Verwende Indizes ab null konsequent.
- Stelle Positionen in Textströmen mit gespeicherten Positionsangaben wieder her, ohne Versätze zu berechnen.
rewindsetzt beide Indikatoren zurück; ein erfolgreichesfseeksetzt den Dateiendeindikator zurück.- Die Positionierung benötigt einen Datenstrom, der sie unterstützt, und darstellbare Versätze.
4.2 Datensätze fester Größe an ihrer bestehenden Dateiposition aktualisieren
Ein Datenstrom im Aktualisierungsmodus liest und schreibt dieselbe Datei. Verwende "r+b", um eine vorhandene Binärdatei beim Ändern ihrer Datensätze zu erhalten. "w+b" löscht den alten Dateiinhalt sofort, und Anhängemodi erzwingen jeden Schreibvorgang am Ende.
Zulässig zwischen Lesen und Schreiben wechseln
Für einen Datenstrom im Aktualisierungsmodus gelten zwei Wechselregeln:
| Wechsel | Erforderliche Aktion zwischen den Vorgängen |
|---|---|
| Schreiben zu Lesen | Ausgabepuffer leeren oder eine Positionierung durchführen |
| Lesen zu Schreiben | Eine Positionierung durchführen, sofern der Lesevorgang nicht auf das Dateiende gestoßen ist |
Zu den Positionierungsfunktionen gehören fseek, fsetpos und rewind. Prüfe Funktionen, die ein Ergebnis melden, bevor du fortfährst. Ein Wechsel ohne die erforderliche Aktion führt zu undefiniertem Verhalten; du kannst nicht vorhersagen, dass er sich nur auf den nächsten Datensatz auswirkt.
Einen vollständigen Datensatz aktualisieren
Angenommen, eine Datei ohne Dateikopf enthält fünf 24 Byte große Datensätze in derselben Darstellung wie struct record. Um den Datensatz mit Index 3 zu ändern:
- Öffne mit
"r+b"und prüfe den Zeiger. - Positioniere bei Byte 72 und prüfe den Erfolg.
- Lies genau einen vollständigen Datensatz.
- Setze seine Komponente
scoreauf 99. - Positioniere zurück zum Anfang des Datensatzes.
- Schreibe genau einen Datensatz und prüfe das Ergebnis des Schließens.
Die wichtigsten dazwischenliegenden Vorgänge sind:
if (fread(&rec, sizeof rec, 1, fp) != 1) {
/* Report the incomplete input, then clean up. */
goto cleanup;
}
rec.score = 99;
if (fseek(fp, -(long)sizeof rec, SEEK_CUR) != 0) {
/* Report the failed seek, then clean up. */
goto cleanup;
}
if (fwrite(&rec, sizeof rec, 1, fp) != 1) {
/* Report the failed write, then clean up. */
goto cleanup;
}
Die umgebende Funktion stellt eine Marke cleanup bereit, die auf diesen Pfaden den Datenstrom schließt und einen Fehler zurückgibt. Nach dem Lesen steht die Position bei 96. Das Zurückgehen um 24 Byte wählt den ursprünglichen Datensatz aus und erlaubt zugleich den Wechsel vom Lesen zum Schreiben. Ein erfolgreicher Ersatz lässt die Datei bei 120 Byte mit fünf Datensätzen. Das Wiederherstellen einer gespeicherten Anfangsposition ist eine weitere zulässige Möglichkeit für die Aktualisierung.
Entscheiden, wann neu geschrieben werden muss
Das Anhängen eines 24 Byte großen Datensatzes vergrößert die Datei auf 144 Byte. Das Positionieren am Anfang eines "ab"- oder "a+b"-Datenstroms kann den nächsten Schreibvorgang nicht in ein Überschreiben verwandeln; geschrieben wird weiterhin am aktuellen Ende.
Ein Ersatz an der bestehenden Position muss in den ersetzten Platz passen. Die Änderung von "Bo,7" zu "Bo,9" passt. Wird "Bo,12" über "Bo,7\n" geschrieben, wird der Zeilenumbruch verbraucht und der folgende Text an den Datensatz angehängt. Um Datensätze zu vergrößern oder zu verkleinern, schreibe eine korrigierte neue Datei. Standard-C hat keine Datenstromfunktion, die einen Bytebereich aus der Mitte einer Datei entfernt. Beim Löschen können stattdessen alle verbleibenden Datensätze kopiert oder ein Datensatz fester Größe als gelöscht markiert werden.
Den Austausch wiederherstellbar halten
Prüfe remove und rename; beide geben bei Erfolg 0 zurück. Ob rename ein vorhandenes Ziel ersetzt, ist implementierungsdefiniert. Linux und macOS unterstützen das Ersetzen; die C-Bibliothek von Microsoft schlägt fehl, wenn das Ziel existiert.
Ein portables Vorgehen zur Wiederherstellung schreibt und schließt die neue Datei, benennt das Original in einen anderen Sicherungsnamen um und benennt die neue Datei in den Originalnamen um. Schlägt das Einsetzen fehl, halte die Sicherung zur Wiederherstellung verfügbar und melde, wo sie liegt. Lösche die Sicherung erst, nachdem das Einsetzen erfolgreich war. Diese Abfolge ermöglicht eine Wiederherstellung; sie ist keine portable atomare Transaktion und keine Garantie gegen Stromausfall.
Wichtige Erkenntnisse
- Verwende
"r+b", um eine vorhandene Datei zu aktualisieren, ohne ihren Inhalt zu löschen. - Beachte die Wechselregel für Lesen und Schreiben, auch wenn die beabsichtigte Position offensichtlich scheint.
- Positioniere nach dem Lesen eines Datensatzes zurück, bevor du ihn ersetzt.
- Im Anhängemodus wird immer am Ende geschrieben.
- Schreibe Daten neu, wenn sich ihre Länge ändert.
- Prüfe jeden Schritt beim Austausch und bewahre ein wiederherstellbares Original auf.
5Dateifehler erkennen, melden und behandeln
Ein Dateivorgang kann nach erfolgreichem Öffnen fehlschlagen, und ein Fehler bei gepufferter Ausgabe kann erst beim Schließen auftreten. Zuverlässige Programme lesen Rückgabewerte, unterscheiden ungültige Daten von Zugriffsproblemen, bewahren hilfreiche Fehlerinformationen und geben auf jedem Pfad Ressourcen frei. Diese Aufgaben greifen ineinander: Die Diagnosemeldung soll den ursprünglichen Fehler erklären, während der endgültige Status auch Fehler bei der Ressourcenfreigabe widerspiegeln muss. Ein vollständiges Kopierprogramm zeigt, wie diese Entscheidungen in den üblichen Kontrollfluss von C passen.
5.1 Jedes Ergebnis prüfen und das Dateiende von Fehlern unterscheiden
Eine Datei kann erfolgreich geöffnet werden und dennoch beim Lesen, Schreiben, Positionieren oder Schließen einen Fehler verursachen. Ein robustes Programm prüft das Ergebnis des tatsächlich versuchten Vorgangs. Es unterscheidet einen abgeschlossenen Lesevorgang von einer unterbrochenen Eingabe und fehlerhaft aufgebaute Daten von einem fehlerhaften Datenstrom.
Die einzelnen Fehlersignale kennenlernen
| Vorgang | Zu behandelndes Signal |
|---|---|
fopen, fgets | NULL |
fgetc, fputc, fflush, fclose | EOF |
fprintf | Ein negatives Ergebnis |
fputs | EOF |
fread, fwrite | Weniger vollständige Elemente als angefordert |
fscanf | Weniger Zuweisungen als erwartet oder EOF |
fseek, fgetpos, fsetpos | Ein Ergebnis ungleich null |
ftell | -1L |
rewind, clearerr und perror geben keinen Wert zurück. Muss die erfolgreiche Positionierung am Anfang geprüft werden, verwende fseek, statt dich auf ein Ergebnis von rewind zu verlassen.
Den Datenstrom nach dem Ende des Lesens prüfen
Der Datenstrom führt getrennte Dateiende- und Fehlerindikatoren. feof(fp) prüft den ersten, ferror(fp) den zweiten. Beide geben einen Wert ungleich null zurück, wenn der jeweilige Indikator gesetzt ist.
while (fgets(line, sizeof line, fp) != NULL) {
/* Process successful input. */
}
if (ferror(fp)) {
fprintf(stderr, "Input could not be completed\n");
/* Return failure after cleanup. */
}
Der Rückgabewert von fgets allein unterscheidet die beiden Gründe für das Anhalten nicht. Ebenso gibt fgetc sowohl am Dateiende als auch bei einem Lesefehler EOF zurück, und fread gibt in beiden Fällen eine zu kleine Anzahl zurück. Prüfe feof nicht vor dem Lesen, um vorherzusagen, ob Daten verbleiben.
Indikatoren bleiben gesetzt. clearerr setzt beide zurück, ebenso rewind. Ein erfolgreiches fseek oder fsetpos setzt den Dateiendeindikator zurück, aber keinen vorhandenen Fehlerindikator. Das Zurücksetzen eines Indikators quittiert ihn; es verbindet kein entferntes Laufwerk erneut und repariert keine beschädigte Datei. Versuche es erst erneut, wenn die Ursache behoben wurde.
Ungültige Daten von fehlgeschlagenem Zugriff unterscheiden
Gibt fscanf(fp, "%d", &value) bei "abc" den Wert 0 zurück, ist die Konvertierung vor jeder Zuweisung fehlgeschlagen. Dafür muss weder das Dateiende erreicht noch ein Lesefehler aufgetreten sein. Melde ein ungültiges Feld, möglichst mit einer Zeilennummer. Eine Meldung über den Datenträger würde das falsche Problem beschreiben.
Wird ein erforderlicher Dateikopf als ein Element gelesen und gibt fread den Wert 0 zurück, ist der Dateikopf unvollständig. Untersuche dieses teilweise gefüllte Objekt nicht. Ist ferror gesetzt, melde einen Eingabefehler; endete die Datei, melde abgeschnittene Daten. Eine leere Datei und ein unvollständiger Dateikopf können dieselbe Elementanzahl ergeben. Das erwartete Format legt daher fest, dass der Dateikopf vorhanden sein muss.
Verzögerte Ausgabefehler erkennen
Ein Datenträger kann voll werden, nachdem das Programm seine Ausgabe geöffnet hat. Gepufferte Schreibvorgänge können erfolgreich erscheinen, bis fflush oder fclose die ausstehenden Bytes tatsächlich überträgt. Prüfe das abschließende Schließen und lass einen Fehler eine ansonsten erfolgreiche Aufgabe fehlschlagen. Schließe denselben Datenstrom danach nicht erneut.
An einem Terminal signalisiert unter Linux oder macOS Strg+D am Anfang einer Zeile beziehungsweise unter Windows Strg+Z gefolgt von der Eingabetaste gewöhnlich das Eingabeende. clearerr(stdin) setzt den daraus entstehenden Indikator zurück, falls das Programm einen weiteren Versuch benötigt. Die Terminaleingabe und ein Lesefehler bleiben unterschiedliche Ursachen.
Wichtige Erkenntnisse
- Prüfe jede Funktion anhand ihres eigenen dokumentierten Rückgabewerts.
- Lies zuerst und ermittle danach den Grund für das Anhalten anhand der Datenstromindikatoren.
- Ein nicht passendes Format bedeutet ungültige Daten, nicht unbedingt einen Datenstromfehler.
- Weise unvollständige erforderliche Dateiköpfe und Datensätze zurück.
- Ein verzögerter Fehler beim Schließen der Ausgabe muss die Aufgabe fehlschlagen lassen.
5.2 Fehler mit errno, perror und strerror erklären
Ein Rückgabewert zeigt dir, dass ein Vorgang fehlgeschlagen ist. Ein Fehlercode kann helfen zu erklären, warum. Verwende errno nur im Rahmen des dokumentierten Verhaltens des Vorgangs und sichere den Wert, bevor nicht zusammenhängende Bibliotheksaufrufe ihn ändern können.
Den Fehler anhand des Vorgangs feststellen
errno ist ein veränderbarer int-Ausdruck aus <errno.h> mit threadlokaler Speicherdauer in C11. Sein Anfangswert ist null. Bibliotheksaufrufe setzen ihn nicht auf null zurück, und ein erfolgreicher Aufruf kann einen Wert ungleich null hinterlassen oder setzen. Eine Prüfung von errno nach einem erfolgreichen fopen zeigt daher nicht an, dass das Öffnen fehlgeschlagen ist.
Auf POSIX-Systemen setzt ein fehlgeschlagenes fopen einen Fehlercode. Auch die C-Bibliothek von Microsoft stellt Dateifehlercodes bereit. Standard-C verlangt nicht, dass fopen errno setzt. Für eine portable Meldung kann der Wert vor dem Öffnen zurückgesetzt und eine allgemeine Meldung verwendet werden, falls kein Code geliefert wurde:
errno = 0;
FILE *fp = fopen(name, "r");
if (fp == NULL) {
int saved = errno;
if (saved != 0)
fprintf(stderr, "Cannot open %s: %s\n",
name, strerror(saved));
else
fprintf(stderr, "Cannot open %s\n", name);
return EXIT_FAILURE;
}
Dieser Ausschnitt benötigt außerdem <stdio.h>, <stdlib.h> und <string.h>. Das sofortige Speichern des Codes verhindert, dass spätere Aufrufe zur Meldung oder Ressourcenfreigabe den ursprünglichen Grund ersetzen. Ein gespeicherter Code ist auch dann nützlich, wenn die endgültige Meldung erst viel später ausgegeben wird.
Eine Funktion für die Meldung wählen
perror(label) schreibt die Bezeichnung, einen Doppelpunkt und ein Leerzeichen, die aktuelle Fehlermeldung und einen Zeilenumbruch nach stderr. Eine typische Meldung für eine fehlende Datei lautet "missing.txt: No such file or directory"; der genaue Wortlaut hängt vom System ab.
strerror(code) gibt die Meldung der Bibliothek für einen übergebenen Code zurück und eignet sich daher für eine eigene Diagnosemeldung, die Programm, Vorgang und Datei nennt. Verändere die zurückgegebene Zeichenkette nicht. Verwende nach der Ressourcenfreigabe strerror(saved), statt den neuen Wert von errno abzufragen.
C definiert EDOM, ERANGE und EILSEQ. Gängige Dateifehlercodes wie ENOENT, EACCES, EEXIST und ENOSPC werden von POSIX und weit verbreiteten C-Bibliotheken bereitgestellt. Stimme deinen Code auf die verwendete Schnittstelle und Plattform ab. Gehe nicht davon aus, dass numerische Codewerte oder der genaue englische Meldungstext überall gleich sind.
Einen mehrdeutigen Rückgabewert behandeln
Bei manchen Funktionen kann das Ergebnis allein nicht zwischen Erfolg und einem Wertebereichsfehler unterscheiden. strtol kann LONG_MAX als gültige Zahl oder als Ergebnis eines Überlaufs zurückgeben. Setze errno vor der Konvertierung zurück und prüfe dann den dokumentierten Wertebereichsfehlercode:
errno = 0;
char *end;
long value = strtol(text, &end, 10);
if (end == text) {
/* No digits were converted. */
} else if (errno == ERANGE) {
/* The value is outside the long range. */
} else {
/* Validate trailing characters and the target range. */
}
Eine hilfreiche Diagnosemeldung gibt der nutzenden Person einen Anhaltspunkt zum Handeln. Nenne die Datei und den versuchten Vorgang; bewahre den vom System angegebenen Grund, falls verfügbar. Sende die Meldung an stderr, damit stdout für gültige Ergebnisdaten verfügbar bleibt.
Wichtige Erkenntnisse
- Stelle den Fehler anhand des Funktionsergebnisses fest, bevor du
errnointerpretierst. - Speichere
errnounmittelbar vor Aufrufen zur Meldung oder Ressourcenfreigabe. perrorverwendet das aktuelleerrno;strerrorakzeptiert einen gespeicherten Code.- Setze
errnovorstrtolzurück und prüfeERANGE. - Nenne in einer Diagnosemeldung die Datei und den versuchten Vorgang.
5.3 Ressourcen freigeben und das Ergebnis melden
Die Fehlerbehandlung ist erst vollständig, wenn Ressourcen freigegeben sind und das Programm das tatsächliche Ergebnis meldet. Plane beim Öffnen eines Datenstroms, wer dafür verantwortlich ist, und halte diesen Plan auf jedem späteren Pfad ein.
Jedem Öffnen ein Schließen zuordnen
Schlägt das Öffnen der Eingabe fehl, gibt es keinen Datenstrom zu schließen. Schlägt das Öffnen der Ausgabe nach erfolgreichem Öffnen der Eingabe fehl, schließe die Eingabe. Schlägt das Kopieren fehl, nachdem beide geöffnet wurden, schließe beide, prüfe das Schließen der Ausgabe und behalte einen Fehlerstatus bei. Einen Zeiger zweimal zu schließen bietet keinen zusätzlichen Schutz, sondern verwendet einen bereits abgeschlossenen Datenstrom.
Code mit vorzeitigen Rückgaben schließt Ressourcen gewöhnlich in umgekehrter Reihenfolge. Eine gemeinsame Aufräummarke ist nützlich, wenn mehrere Fehlerpfade dieselben Schritte benötigen. Initialisiere Zeiger mit NULL, behalte standardmäßig einen Fehlerstatus bei und setze den Erfolgsstatus erst, nachdem die Verarbeitungsphase abgeschlossen ist.
Das folgende vollständige Programm kopiert unterschiedliche Ein- und Ausgabedateien im Binärmodus. Es verwendet allgemeine Meldungen, weil Standard-C nicht verlangt, dass jeder stdio-Fehler errno setzt.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *in = NULL, *out = NULL;
int status = EXIT_FAILURE;
int output_acquired = 0;
unsigned char buf[512];
size_t n;
if (argc != 3) {
fprintf(stderr, "Usage: %s input output\n", argv[0]);
return EXIT_FAILURE;
}
in = fopen(argv[1], "rb");
if (in == NULL) {
fprintf(stderr, "Cannot open input: %s\n", argv[1]);
goto cleanup;
}
out = fopen(argv[2], "wb");
if (out == NULL) {
fprintf(stderr, "Cannot open output: %s\n", argv[2]);
goto cleanup;
}
output_acquired = 1;
while ((n = fread(buf, 1, sizeof buf, in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
fprintf(stderr, "Write failed: %s\n", argv[2]);
goto cleanup;
}
}
if (ferror(in)) {
fprintf(stderr, "Read failed: %s\n", argv[1]);
goto cleanup;
}
status = EXIT_SUCCESS;
cleanup:
if (out != NULL && fclose(out) == EOF) {
fprintf(stderr, "Close failed: %s\n", argv[2]);
status = EXIT_FAILURE;
}
if (in != NULL && fclose(in) == EOF) {
fprintf(stderr, "Close failed: %s\n", argv[1]);
status = EXIT_FAILURE;
}
if (output_acquired && status != EXIT_SUCCESS) {
if (remove(argv[2]) != 0)
fprintf(stderr, "Cannot remove incomplete output: %s\n",
argv[2]);
}
return status;
}
Die Eingabeschleife verarbeitet nur tatsächlich gelesene Bytes. Ein unvollständiger Schreibvorgang springt zur Ressourcenfreigabe; ein Eingabefehler wird nach der Schleife geprüft. Das Schließen der Ausgabe kann den Erfolg noch in einen Fehler verwandeln. Das Entfernen wird nur versucht, wenn dieser Programmlauf die Ausgabe erfolgreich geöffnet hat, sodass ein Fehler beim Öffnen nicht das Löschen einer anderen Datei auslöst. Die Forderung nach unterschiedlichen Dateien ist wichtig: "wb" würde die Eingabe löschen, wenn beide Namen dieselbe Datei bezeichnen.
Wichtige vorhandene Daten erhalten
Das direkte Öffnen der Ausgabe kann das Löschen des Inhalts nicht rückgängig machen. Um wertvolle Daten zu ersetzen, schreibe zunächst unter einem anderen temporären Namen. Prüfe jeden Schreibvorgang und das abschließende Schließen vor dem Umbenennen. Behalte das Original unter einem Sicherungsnamen, bis die neue Datei eingesetzt ist; prüfe jedes Umbenennen und Entfernen. Schlägt das Einsetzen fehl, melde den Speicherort der wiederherstellbaren Datei, statt sie zu löschen.
Eine Hilfsfunktion sollte schließen, was sie geöffnet hat, und ihrem Aufrufer ein Erfolgs- oder Fehlerergebnis zurückgeben. Der Aufrufer kann entscheiden, ob er es erneut versucht oder das Programm beendet. Die Rückgabe von EXIT_FAILURE nach jedem unvollständigen Schritt ermöglicht es einem Skript, vor einer abhängigen Aktion anzuhalten, etwa vor dem Löschen des Originals nach einer Sicherung.
Wichtige Erkenntnisse
- Lege die Verantwortung für Datenströme fest und behalte standardmäßig einen Fehlerstatus bei.
- Schließe jeden erfolgreich geöffneten Datenstrom auf jedem Pfad genau einmal.
- Lass einen Fehler beim Schließen den endgültigen Status ändern.
- Entferne unvollständige Ausgabe nur, wenn dieser Programmlauf sie erstellt oder geöffnet hat.
- Schreibe Ersatzdateien getrennt und bewahre das Original auf, bis das Einsetzen gelingt.
- Hilfsfunktionen melden Fehler an ihre Aufrufer, damit diese über die Behandlung entscheiden können.
Dateiein- und -ausgabe in C: Beispiele
1Einen Datenstrom öffnen, ohne vorhandene Daten zu verlieren
12 Schritte
- 1EinführungEntscheiden, was erhalten bleiben muss
Ein Programm soll Ergebnisse speichern. Entscheide, bevor es etwas öffnet, ob alte Daten erhalten bleiben müssen und ob die Datei lesbare Zeilen oder exakte Bytes enthält. Diese Entscheidungen bestimmen den Modus; ein falscher Modus kann alte Daten löschen.
Du verfolgst einen Schreibvorgang vom Text des Programms durch seinen Datenstrom (den Kanal der Bibliothek zu einer Datei oder einem Terminal) und entscheidest dann, wohin normale Ergebnisse und Diagnosemeldungen gehen sollen (die Standarddatenströme). Das Öffnen wirkt bereits auf die Datei, daher fällt diese Entscheidung vor dem ersten Schreibvorgang.
- 2ErklärungÜber einen Datenstrom auf Daten zugreifen
Ein Datenstrom ist der Kanal der Standardbibliothek zu einer Datei, einem Terminal oder einer Pipe. Speichere seinen Zeiger als
FILE *aus<stdio.h>und übergib ihn an Funktionen wiefprintf.Das Bibliotheksobjekt speichert den Puffer, die Position und die Statusindikatoren. Du änderst seine Felder nicht. Eine Dateiposition rückt bei der Verarbeitung von Daten vor; ein Terminal oder eine Pipe kann bei der Positionierung nicht wie eine gewöhnliche Datei behandelt werden.
- 3BeispielZeilenumbrüche bis zu den gespeicherten Bytes verfolgen
Das Schreiben von
"a\nb\nc\n"liefert drei Buchstaben und drei Zeilenumbruchzeichen. Unter Linux oder macOS enthält die Textdatei sechs Byte. Unter Windows wird jeder Zeilenumbruch zu CR LF, sodass neun Byte gespeichert werden.Das Lesen dieser Windows-Datei im Windows-Textmodus stellt sechs Zeichen wieder her. Die binäre Eingabe macht alle neun Bytes sichtbar. Der Text des Programms und die gespeicherte Bytefolge sind unterschiedliche Ansichten derselben Zeilen.
- 4VerbindungEinen Modus für ein Bild wählen
Eine Bytefolge in einem komprimierten Bild kann Werte enthalten, die der Textmodus umwandelt. Das Hinzufügen von
bwählt den Binärmodus und erhält diese Werte.Das erklärt, warum ein Bildleseprogramm, das unter Linux mit
"r"funktioniert, unter Windows fehlschlagen kann. Es muss"rb"verwenden, weil es eine festgelegte Bytefolge verarbeitet und keine Zeilen, die die Bibliothek umwandeln soll. - 5HerausforderungEine Zeichenanzahl prüfen
Unter Windows speichert ein Schreibvorgang im Textmodus
"m\nn\n". Wie viele Bytes enthält die entstehende Datei und warum?---reveal---
Sie enthält sechs Byte: zwei Buchstaben und zwei CR-LF-Paare. Die vier vom Programm gelieferten Zeichen werden erweitert, weil jeder Zeilenumbruch zu zwei gespeicherten Bytes wird. Im Binärmodus würden dieselben vier gelieferten Bytes ohne diese Umwandlung geschrieben.
- 6ErklärungVor dem Zugriff über den Datenerhalt entscheiden
Bei einer vorhandenen Datei erlaubt
"r"das Lesen,"w"löscht den Inhalt und erlaubt das Schreiben, und"a"erhält die Daten und hängt an. Ein+erlaubt sowohl Ein- als auch Ausgabe.Verwende
"r+", um eine vorhandene Datei für Aktualisierungen zu erhalten."w+"löscht ihren Inhalt dagegen. Das in C11 eingeführte"wx"schlägt fehl, wenn die Datei existiert, und ist daher nützlich, wenn ein vorhandenes Ziel geschützt werden muss. - 7TippEinen Datenstrom öffnen und schließen
Prüfe nach
FILE *fp = fopen(name, "r");vor jedem Datenstromvorgangfp == NULL. Bei einem Fehler gibt es keinen Datenstrom zu verarbeiten oder zu schließen.Bei Erfolg besitzt das Programm einen Datenstrom, der ein
fclosebenötigt. Prüfe auch dieses Schließen. Der Datenstrom ist nach dem Aufruf abgeschlossen, selbst wenn das Schließen fehlschlägt; ein erneuter Schließversuch verwendet einen nicht mehr gültigen Datenstrom. - 8HerausforderungDas alte Protokoll schützen
Ein vorhandenes Protokoll muss seine Einträge behalten, und eine neue Zeile soll hinzugefügt werden. Eine lernende Person wählt
"w", weil die Aufgabe etwas schreibt. Welcher Modus sollte verwendet werden und wann würde"w"die alten Einträge zerstören?---reveal---
Verwende
"a". Der Modus"w"zerstört vorhandene Inhalte, sobald das Öffnen gelingt, noch vor einem Schreibvorgang. Die Wahl muss das Protokoll bereits beim Öffnen schützen, nicht erst im späteren Verarbeitungscode. - 9ErklärungErgebnisse von Erklärungen trennen
Die bereits geöffneten Datenströme sind
stdinfür Eingaben,stdoutfür Ergebnisse undstderrfür Diagnosemeldungen. Eine Funktion, dieFILE *outentgegennimmt, kann dasselbe Format in eine Datei oder nachstdoutschreiben.Eine Shell kann die Standardausgabe in eine Datei umleiten oder über eine Pipe an ein anderes Programm senden. Halte Diagnosetext auf
stderr, damit ein maschinenlesbares Ergebnis keine Fehlermeldungen zwischen seinen Datenzeilen erhält. - 10KernaussageGepufferten Text sichtbar machen
Ausgaben können in einem Speicherpuffer warten. Die Standardausgabe auf einem Terminal ist gewöhnlich zeilengepuffert; umgeleitete Ausgabe ist gewöhnlich vollständig gepuffert. Die Standardfehlerausgabe ist nicht vollständig gepuffert.
Eine Aufforderung ohne Zeilenumbruch kann vor Beginn der Eingabe ein geprüftes
fflush(stdout)benötigen. Das Leeren des Puffers verwirft keine Eingabe. Um eine unerwünschte Eingabezeile zu überspringen, lies Zeichen bis zu ihrem Zeilenumbruch oder bis zum Dateiende. - 11HerausforderungDie Ausgabe eines Filters verfolgen
Ein Filterprogramm liest die Standardeingabe, schreibt Ergebnisse auf die Standardausgabe und Fehler auf die Standardfehlerausgabe. Es wird als
filter < raw.txt > clean.txtausgeführt. Wo sollte eine Meldung über einen zurückgewiesenen Datensatz erscheinen und welchen Status sollte das Programm zurückgeben, wenn es die Aufgabe nicht abschließen kann?---reveal---
Die Diagnosemeldung geht an das geerbte Fehlerziel, gewöhnlich das Terminal. Gib
EXIT_FAILUREzurück. Die Ergebnisdatei bleibt von der Meldung getrennt, und die Shell kann den Fehler erkennen. - 12ZusammenfassungDen Lebenszyklus eines Datenstroms bewusst gestalten
Entscheide vor dem Öffnen über den Erhalt vorhandener Daten und das Verhalten im Text- oder Binärmodus. Prüfe, ob das Öffnen einen Datenstrom geliefert hat, verarbeite Daten nur über gültige Zeiger und schließe jeden Datenstrom, den du besitzt, einmal.
Standarddatenströme ermöglichen es der Shell, die Verbindungen bereitzustellen. Trenne Ergebnisse von Diagnosemeldungen und gib einen Status zurück, der die abgeschlossene Aufgabe beschreibt. So kann dasselbe kleine Programm interaktiv, mit Dateien oder innerhalb einer Pipeline arbeiten.
2Einen Textdatensatz lesen und seinen Inhalt prüfen
15 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungAuf der passenden Ebene lesen
- 2ErklärungEin Zeichen von EOF unterscheidbar halten
- 3BeispielNur erfolgreiche Lesevorgänge zählen
- 4HerausforderungDie letzte Zeile ohne Zeilenumbruch mitzählen
- 5BeispielEin festgelegtes Format schreiben
- 6TippPlatz für das abschließende Nullzeichen reservieren
- 7ErklärungDie Zuweisungsanzahl verwenden
- 8HerausforderungEinen Namen mit begrenzter Größe formatieren
- 9ErklärungEine Zeile in Speicher bekannter Größe lesen
- 10BeispielEine Zeile über zwei Lesevorgänge verfolgen
- 11TippDas Zeilenende entfernen, ohne Daten zu löschen
- 12ErklärungDie bereits gelesene Zeile parsen
- 13HerausforderungEine genau passende Zeile annehmen
- 14HerausforderungEin Präfix mit ungültigem Rest zurückweisen
- 15ZusammenfassungLesen und Interpretation getrennt halten
3Bytes übertragen und ihre Bedeutung festlegen
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungBytes von Werten unterscheiden
- 2ErklärungFestlegen, was der Rückgabewert zählt
- 3BeispielDen letzten unvollständigen Block verfolgen
- 4KernaussageEinen Datensatz von einem Byteblock unterscheiden
- 5HerausforderungDie tatsächlich gelesene Anzahl verwenden
- 6ErklärungDas Format nicht vom Compiler festlegen lassen
- 7BeispielEinen Little-Endian-Wert dekodieren
- 8BeispielEinen Datensatz anhand seines Aufbaus finden
- 9HerausforderungDie angegebene Datenmenge prüfen
- 10VerbindungDer vom Format erlaubten Struktur folgen
- 11HerausforderungVor dem Dekodieren ein vollständiges Feld verlangen
- 12ZusammenfassungÜbertragung und Format aufeinander abstimmen
4Einen Datensatz finden und sicher ersetzen
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungZu den benötigten Daten springen
- 2ErklärungEinen Bezugspunkt wählen
- 3BeispielDen Datensatz mit Index 3 finden
- 4TippPositionen in Textströmen als Positionsangaben wiederherstellen
- 5HerausforderungDen dritten Datensatz finden
- 6ErklärungDie vorhandene Datei erhalten
- 7BeispielDen Anfang des Datensatzes wiederherstellen
- 8KernaussageDen Ersatz abschließen
- 9HerausforderungSowohl Modus als auch Positionierung wählen
- 10VerbindungBei einem wachsenden Datensatz neu schreiben
- 11TippDas Einsetzen wiederherstellbar halten
- 12HerausforderungEinen abgeschlossenen Anhängevorgang interpretieren
- 13ZusammenfassungPositionierung gehört zu einer zulässigen Aktualisierung
5Fehler bei Meldung und Ressourcenfreigabe berücksichtigen
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungZum Erfolg gehört das abschließende Schließen
- 2ErklärungJedes Funktionsergebnis lesen
- 3ErklärungErklären, warum die Eingabe endete
- 4HerausforderungEinen abgeschnittenen Dateikopf zurückweisen
- 5ErklärungDen ursprünglichen Grund festhalten
- 6BeispielEine Meldung aus dem gespeicherten Code ausgeben
- 7HerausforderungDie richtige Quelle für die Diagnose wählen
- 8ErklärungFesthalten, welche Datenströme geöffnet wurden
- 9BeispielDen Erfolgsstatus bei der Ressourcenfreigabe ändern
- 10HerausforderungDie nächste Aktion eines Skripts bestimmen
- 11KernaussageDas Original nach einem Umbenennungsfehler behalten
- 12ZusammenfassungDas Ergebnis der gesamten Aufgabe zurückgeben
Dateiein- und -ausgabe in C: Übungsaufgaben
Set 1: Datenströme wählen und vollständige Eingaben verarbeiten10 Übungsfragen
Frage 1 von 10
Antwort zeigenAntwort ausblenden
"rb" öffnet eine vorhandene Datei zur binären Eingabe und erhält ihre gespeicherten Bytes. Das einfache "r" wählt den Textmodus, der unter Windows Zeilenenden umwandeln und Strg+Z als Eingabeende erkennen kann. Die Schreibmodi bieten nicht den geforderten Lesezugriff.Frage 2 von 10
Antwort zeigenAntwort ausblenden
"w" würde die früheren Protokolleinträge löschen, sobald das Öffnen gelingt. "r" erhält die Daten, erlaubt aber nicht das Schreiben des neuen Eintrags; "w+" erlaubt Ein- und Ausgabe, löscht aber ebenfalls den Dateiinhalt.Frage 3 von 10
fgets liest die letzte Zeile erfolgreich; sie enthält "maple" ohne Zeilenumbruch. Was bleibt nach line[strlen(line) - 1] = '\0'; übrig?Antwort zeigenAntwort ausblenden
"maple". fgets erfindet keinen Zeilenumbruch, wenn die Datei keinen enthält. Suche vor dem Entfernen mit strcspn nach CR oder LF, damit eine letzte Zeile ohne Zeilenende alle tatsächlichen Daten behält.Frage 4 von 10
0xFF und enthält weitere Daten. Warum kann char c; while ((c = fgetc(fp)) != EOF) sofort anhalten?Antwort zeigenAntwort ausblenden
fgetc gibt das Byte als nicht negativen int zurück, aber beim Speichern in einem vorzeichenbehafteten 8-Bit-char kann aus 255 der Wert -1 werden. Dieser Wert kann beim Vergleich dem EOF der Implementierung entsprechen und die Schleife vor den späteren Bytes beenden. Die Klammern sind hier korrekt; der verengende Zieltyp verursacht das Problem.Frage 5 von 10
char tag[14]; int count;. Welcher Aufruf liest sicher ein Wort und eine Ganzzahl in diese Objekte?Antwort zeigenAntwort ausblenden
&count, einen Zeiger auf das Ziel. Die Breite 14 sieht wie die Arraygröße aus, erlaubt aber ein Eingabezeichen mehr, als das Array zusammen mit dem abschließenden Nullzeichen sicher aufnehmen kann.Frage 6 von 10
Antwort zeigenAntwort ausblenden
stderr hält Diagnosemeldungen von den Berichtsdaten getrennt, die an stdout gesendet werden. Eine Ausgabe der Meldung auf stdout würde sie in den umgeleiteten Bericht aufnehmen und könnte dessen erwartetes Format beschädigen. Die Standardeingabe liefert Daten zum Lesen; sie ist nicht das Ziel einer Diagnosemeldung.Frage 7 von 10
fwrite(a, 4, 7, fp) schreibt jedes angeforderte Element. Was gibt der Aufruf zurück?Antwort zeigenAntwort ausblenden
fwrite gibt die Anzahl der vollständig geschriebenen Elemente zurück, also 7. Die Übertragung umfasst 28 Byte, weil jedes Element die Größe 4 hat, aber diese Byteanzahl ist nicht der Rückgabewert. Byte- und Elementanzahl stimmen nur bei der Elementgröße 1 überein.Frage 8 von 10
07 02 00 00. Welchen Wert stellt es dar?Antwort zeigenAntwort ausblenden
07 02 als Big-Endian-Paar gelesen, ergäbe sich 1.794. Die eigene Byte-Reihenfolge des Rechners ändert die vom Dateiformat festgelegte Interpretation nicht.Frage 9 von 10
Antwort zeigenAntwort ausblenden
SEEK_CUR würde diese absolute Position zur aktuellen Position addieren, statt sie auszuwählen.Frage 10 von 10
fread(&h, 16, 1, fp) gelesen. Der Aufruf gibt 0 zurück, ferror(fp) ist null und feof(fp) ist ungleich null. Wie sollte das Leseprogramm vorgehen?Antwort zeigenAntwort ausblenden
ferror macht die Interpretation seiner Felder daher nicht sicher.Set 2: Textgrenzen und Binäraufbauten behandeln10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
/work/run gestartet und öffnet "data/items.txt". Welcher Pfad wird aufgelöst?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
producer | consumer > result.txt die Standardfehlerausgabe des ersten Programms standardmäßig?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
fread(buf, 1, 600, in) kopiert. Welche Größen sollten die erfolgreichen Schreibvorgänge haben?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
char name[8]; wird mit snprintf(name, sizeof name, "part-%d", 2468) gefüllt; der Aufruf gibt 9 zurück. Was sollte das Programm daraus schließen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
"cedars\nmaplesx". Das Programm ruft wiederholt fgets(buf, 8, fp) auf. Welche Datensatzgrenzen muss es erkennen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
"north\nsouth\neast". Wie viele Zeilen und Zeilenumbruchzeichen enthält sie?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
uint32_t bereit, unterscheiden sich aber in Byte-Reihenfolge und Füllbytes in Strukturen. Ein Programm schreibt struct { char kind; uint32_t count; } mit einem fwrite roh in ein Dateiformat. Welche Änderung macht die Darstellung von beiden Unterschieden unabhängig?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
long p = ftell(fp); gespeichert und geprüft. Welcher zulässige Aufruf kehrt nach weiterem Lesen dorthin zurück?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
"r+b"-Datenstrom positioniert zu Byte 40 und liest ohne EOF einen vollständigen Datensatz. Ein Feld wird geändert. Welcher Vorgang vor dem Schreiben erlaubt den Wechsel und wählt zugleich denselben Datensatz?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 3: Datensätze aktualisieren und verlässliche Ergebnisse sichern10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
"x\ny\n" im Textmodus geschrieben. Wie viele gespeicherte Bytes ergeben sich?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
"64km" wird an strtol übergeben, nachdem errno zurückgesetzt wurde. Der Rückgabewert ist 64, errno bleibt 0 und end zeigt auf k. Das Feld muss eine vollständige Ganzzahl mit optionalen nachfolgenden Leerraumzeichen sein. Welche Entscheidung ist richtig?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
fread(b, 16, 1, fp) mit einem 16 Byte großen Array. Was gibt der zweite Aufruf zurück und wie darf sein Element verwendet werden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
"18 pear 26". Nach einem erfolgreichen fscanf(fp, "%d", &n) gibt derselbe Aufruf zweimal 0 zurück. Warum hat er 26 nicht erreicht?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
"r+b"-Datenstrom zu Byte 38 und liest ohne EOF einen vollständigen Datensatz. Es ändert ein Feld. Welcher nächste Vorgang wählt denselben Datensatz und erlaubt das Schreiben des Ersatzes?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 4: Fehler in einem vollständigen Dateiablauf untersuchen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
"ready" schlägt fehl, obwohl die sichtbaren Buchstaben übereinstimmen. Welches erhaltene Zeichen erklärt das?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
totals.txt anhängen. Welcher Umleitungsoperator wird benötigt?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
"a+b" positioniert ein Programm zu Byte 12, liest einen Datensatz, ändert seinen Punktestand auf 25, positioniert zu Byte 12 zurück und schreibt den Datensatz. Alle Aufrufe gelingen. Welche Punktestände enthalten danach der zweite und der letzte Datensatz?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
char buf[8] geben Lesevorgänge zunächst "8642kg\n" und dann "1234567" zurück; der nächste geprüfte Lesevorgang bestätigt das normale Dateiende. strtol findet 8642 mit nachfolgendem kg im ersten Datensatz und konvertiert alle Ziffern im zweiten, ohne Wertebereichsfehler. Welche Datensätze sind gültig?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
"a\nb". Ein Programm verwendet int c; size_t n = 0, lines = 0; while (c = fgetc(in) != EOF) { ++n; if (c == '\n') ++lines; }. Welche Gesamtwerte ergeben sich und welche Korrektur zählt die Zeilen richtig?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
abschließendes Nullzeichen
fgets lesen darf, ein weiteres Byte.aktuelles Arbeitsverzeichnis
"data/input.txt" bezeichnete Datei.Anhängemodus
"a+b"-Datenstrom kann die Eingabeposition wählen, aber den nächsten Schreibvorgang nicht umleiten.Aufräumpfad
ausfallsicherer Dateiaustausch
B
Besitz von Ressourcen
Big-Endian-Reihenfolge
00 00 01 02.Binärmodus
"rb", damit die Textumwandlung seine komprimierten Bytes nicht verändern kann.blockweises Kopieren
Byte-Reihenfolge
C
clearerr
D
Dateiendeindikator
Dateikopf
Dateiposition
Dateisignatur
Datenstrom
Datenstrom im Aktualisierungsmodus
+-Modus geöffneter Datenstrom, bei dem Ein- und Ausgabe unter Einhaltung von Wechselregeln erlaubt sind."r+b" erhält sie für den Lese- und Schreibzugriff.Direktzugriff
E
Endzeiger
strtol markiert das erste Zeichen nach dem numerischen Präfix, das die Konvertierung akzeptiert hat."86cm" zeigt der Zeiger auf das c, sodass ein Ganzzahlfeld den Rest zurückweisen kann.EOF
int, damit ein tatsächliches Byte nicht mit dieser Markierung verwechselt wird.ERANGE
errno vor strtol zurück, damit ein alter Wertebereichsfehler nicht mit einem neuen verwechselt werden kann.errno
exklusives Erstellen
"wx" schützt einen vorhandenen Bericht davor, beim Öffnen gelöscht zu werden.F
fclose
EOF meldet.fclose(fp) kann dieser Datenstrom nicht erneut verwendet werden, auch nicht nach einem Fehler.Fehlerindikator
fgets NULL zurückgegeben hat, kann ferror einen fehlgeschlagenen Zugriff vom normalen Ende unterscheiden.Feldbreite
%23s lässt in einem 24 Byte großen Zeichenarray Platz für ein abschließendes Nullzeichen.fgetc
int umgewandelten vorzeichenlosen Zeichenwert liest oder EOF zurückgibt.fgetc erfolgreich ein Zeichen zurückgegeben hat.fgets
FILE-Zeiger
FILE *, der Bibliotheksfunktionen Zugriff auf einen Datenstrom und dessen gespeicherten Zustand gibt.fp an fgetc, nachdem du geprüft hast, dass das Öffnen einen gültigen Datenstromzeiger zurückgegeben hat.fopen
NULL zurückgibt.fgetc oder fclose.fpos_t
fgetpos und fsetpos zum Speichern und Wiederherstellen aller erforderlichen Informationen über die Dateiposition verwenden.fputc
EOF zurückgibt.fputc, nachdem er das gelesene Zeichen umgewandelt hat.fread
fseek
ftell
long und gibt bei einem Fehler -1L zurück; eine Position in einem Textstrom muss als gespeicherte Stelle behandelt werden.Füllbytes in Strukturen
char, gefolgt von einem int, kann acht Byte belegen, auch wenn sich ihre Größen zu fünf addieren.fwrite
G
Ganzzahltyp mit exakter Bitbreite
uint32_t-Feld benötigt weiterhin eine ausdrückliche Bytekodierung, damit eine Austauschdatei portabel ist.I
In-place-Aktualisierung
K
Konvertierungsspezifikation
%lf einen double *, während %f ein Ziel vom Typ float * benötigt.L
Little-Endian-Reihenfolge
02 01 00 00 stellen in einem 32-Bit-Feld in Little-Endian-Reihenfolge den Wert 258 dar.M
Maskierung von CSV-Feldern
Ö
Öffnungsmodus
fopen angibt, welcher Zugriff erlaubt ist und ob vorhandener Inhalt erhalten oder gelöscht wird."a" erhält frühere Protokolleinträge und platziert jeden neuen Schreibvorgang am Ende.P
perror
errno-Wert auf die Standardfehlerausgabe, gefolgt von einem Zeilenumbruch.Pipe
Pufferung
R
rename
Rückgabestatus
&& darf nach einer unvollständigen Dateikopie nicht ausgeführt werden.S
snprintf
Standardausgabe
stdout für die normalen Ergebnisse und sonstigen hauptsächlichen Ausgaben eines Programms.stdout eines Konvertierungsprogramms an die Standardeingabe eines anderen Programms.Standardeingabe
stdin, aus dem ein Programm normalerweise seine hauptsächliche Eingabe erhält.< values.txt liest ein Filterprogramm Dateidaten über stdin statt von der Tastatur.Standardfehlerausgabe
stderr für Diagnosemeldungen, unabhängig vom normalen Ergebnisstrom eines Programms.stderr beschrieben werden, während gültige Daten nach stdout gehen.strcspn
"\r\n" als Ausschlussmenge lässt sich ein Zeilenende finden, ohne gewöhnliche Daten am Ende zu löschen.strerror
errno ersetzt haben könnte.T
Textmodus
U
Umleitung
2> kann die Diagnosemeldungen eines Programms in einer separaten Datei speichern.unvollständiges Element
fread nicht enthalten ist.Ü
Übereinstimmungsfehler
"oak" kann 0 zurückgeben, während Dateiende- und Fehlerindikator des Datenstroms nicht gesetzt sind.Z
zeilenweises Parsen
Zurückstellen von Zeichen
ungetc für die nächste Verarbeitungsstufe zurückstellen.Zuweisungsanzahl
fscanf gelesener Datensatz aus zwei Feldern ist nur vollständig, wenn die zurückgegebene Zuweisungsanzahl 2 ist.Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.