Dateiein- und -ausgabe in C

Lerne, wie C-Programme mit der Standardbibliothek für Ein- und Ausgabe Dateien lesen und schreiben. Öffne Dateien im passenden Modus als Datenströme und verbinde Programme mit der Standardeingabe, Standardausgabe und Standardfehlerausgabe. Verarbeite Text zeichenweise, durch formatiertes Lesen und Schreiben sowie zeilenweise mit 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

  1. 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ären
      Beschreiben, 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ßen
      Eine 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 verwenden
      Die 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.
  2. 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 schreiben
      fgetc 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 lesen
      Zahlen 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 lesen
      Text mit fgets zeilenweise lesen, Zeilenumbrüche, lange Zeilen und Windows-Zeilenenden behandeln und jede Zeile nach dem Lesen parsen.
  3. 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 schreiben
      fread 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 entwerfen
      Erklä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.
  4. 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 setzen
      Die 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 aktualisieren
      Eine 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.
  5. 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 unterscheiden
      Wissen, 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ären
      Mit 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 melden
      Jeden Datenstrom auf jedem Programmpfad schließen, keine halb geschriebene Ausgabe zurücklassen und einen Rückgabestatus liefern, der das Ergebnis wahrheitsgemäß angibt.

Dateiein- und -ausgabe in C einfach erklärt

5 Kapitel

1

Dateien 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.

PufferungsartWann die Ausgabe übertragen wird
Vollständig gepuffertWenn der Puffer voll ist oder ausdrücklich geleert beziehungsweise geschlossen wird
ZeilengepuffertAußerdem, wenn ein Zeilenumbruch geschrieben wird
UngepuffertSo 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
ModusVorhandene DateiFehlende DateiZugriff
"r"Behält den InhaltSchlägt fehlLesen ab Anfang
"w"Löscht den InhaltErstellt die DateiSchreiben ab Anfang
"a"Behält den InhaltErstellt die DateiSchreiben am Ende
"r+"Behält den InhaltSchlägt fehlLesen und Schreiben
"w+"Löscht den InhaltErstellt die DateiLesen und Schreiben
"a+"Behält den InhaltErstellt 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 fopen auf NULL, bevor du den Datenstrom verwendest.
  • Relative Pfade beginnen beim aktuellen Arbeitsverzeichnis.
  • Schließe jeden geöffneten Datenstrom einmal und verwende ihn nach fclose nie 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, stdout und stderr, ohne sie zu öffnen.
  • Sende Programmergebnisse an stdout und Diagnosemeldungen an stderr.
  • 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.
2

Textdateien 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 in int, damit Bytewerte von EOF unterscheidbar 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 ungetc ist 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 %s bei der Eingabe auf die Arraygröße minus eins.
  • Vergleiche den Rückgabewert von fscanf mit der genau erwarteten Zuweisungsanzahl.
  • snprintf gibt 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

  • fgets behä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 strtol auf Ziffern, Wertebereichsfehler und nachfolgende Daten.
3

Daten 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

  • fread und fwrite geben 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:

MerkmalWas sich unterscheiden kann
Typgrößeint kann zwei oder vier Byte groß sein; long unterscheidet sich häufig zwischen 64-Bit-Windows und Linux
Byte-ReihenfolgeDas niederwertigste oder höchstwertige Byte kann zuerst stehen
StrukturaufbauFü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.
4

Beliebige 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.

BezugspunktBedeutung des Versatzes
SEEK_SETVom Anfang aus
SEEK_CURVon der aktuellen Position aus
SEEK_ENDVom 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 fseek auf einen Wert ungleich null und ftell auf -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.
  • rewind setzt beide Indikatoren zurück; ein erfolgreiches fseek setzt 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:

WechselErforderliche Aktion zwischen den Vorgängen
Schreiben zu LesenAusgabepuffer leeren oder eine Positionierung durchführen
Lesen zu SchreibenEine 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:

  1. Öffne mit "r+b" und prüfe den Zeiger.
  2. Positioniere bei Byte 72 und prüfe den Erfolg.
  3. Lies genau einen vollständigen Datensatz.
  4. Setze seine Komponente score auf 99.
  5. Positioniere zurück zum Anfang des Datensatzes.
  6. 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.
5

Dateifehler 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
VorgangZu behandelndes Signal
fopen, fgetsNULL
fgetc, fputc, fflush, fcloseEOF
fprintfEin negatives Ergebnis
fputsEOF
fread, fwriteWeniger vollständige Elemente als angefordert
fscanfWeniger Zuweisungen als erwartet oder EOF
fseek, fgetpos, fsetposEin 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 errno interpretierst.
  • Speichere errno unmittelbar vor Aufrufen zur Meldung oder Ressourcenfreigabe.
  • perror verwendet das aktuelle errno; strerror akzeptiert einen gespeicherten Code.
  • Setze errno vor strtol zurück und prüfe ERANGE.
  • 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

5 Interaktive Lektionen

1

Einen Datenstrom öffnen, ohne vorhandene Daten zu verlieren

12 Schritte
Einen Dateimodus wählen, Textumwandlungen nachvollziehen und denselben Verarbeitungscode mit Standarddatenströmen verbinden.
  1. 1
    Einfü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.

  2. 2
    Erklä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 wie fprintf.

    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.

  3. 3
    BeispielZeilenumbrü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.

  4. 4
    VerbindungEinen Modus für ein Bild wählen

    Eine Bytefolge in einem komprimierten Bild kann Werte enthalten, die der Textmodus umwandelt. Das Hinzufügen von b wä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.

  5. 5
    HerausforderungEine 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.

  6. 6
    Erklä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.

  7. 7
    TippEinen Datenstrom öffnen und schließen

    Prüfe nach FILE *fp = fopen(name, "r"); vor jedem Datenstromvorgang fp == NULL. Bei einem Fehler gibt es keinen Datenstrom zu verarbeiten oder zu schließen.

    Bei Erfolg besitzt das Programm einen Datenstrom, der ein fclose benö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.

  8. 8
    HerausforderungDas 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.

  9. 9
    ErklärungErgebnisse von Erklärungen trennen

    Die bereits geöffneten Datenströme sind stdin für Eingaben, stdout für Ergebnisse und stderr für Diagnosemeldungen. Eine Funktion, die FILE *out entgegennimmt, kann dasselbe Format in eine Datei oder nach stdout schreiben.

    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.

  10. 10
    KernaussageGepufferten 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.

  11. 11
    HerausforderungDie 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.txt ausgefü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_FAILURE zurück. Die Ergebnisdatei bleibt von der Meldung getrennt, und die Shell kann den Fehler erkennen.

  12. 12
    ZusammenfassungDen 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.

2

Einen Textdatensatz lesen und seinen Inhalt prüfen

15 Schritte
Von zeichenweisem Lesen über formatierte Werte zu vollständigen Zeilen übergehen und Grenzen behandeln, die häufig Eingabefehler verursachen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungAuf der passenden Ebene lesen
  2. 2
    ErklärungEin Zeichen von EOF unterscheidbar halten
  3. 3
    BeispielNur erfolgreiche Lesevorgänge zählen
  4. 4
    HerausforderungDie letzte Zeile ohne Zeilenumbruch mitzählen
  5. 5
    BeispielEin festgelegtes Format schreiben
  6. 6
    TippPlatz für das abschließende Nullzeichen reservieren
  7. 7
    ErklärungDie Zuweisungsanzahl verwenden
  8. 8
    HerausforderungEinen Namen mit begrenzter Größe formatieren
  9. 9
    ErklärungEine Zeile in Speicher bekannter Größe lesen
  10. 10
    BeispielEine Zeile über zwei Lesevorgänge verfolgen
  11. 11
    TippDas Zeilenende entfernen, ohne Daten zu löschen
  12. 12
    ErklärungDie bereits gelesene Zeile parsen
  13. 13
    HerausforderungEine genau passende Zeile annehmen
  14. 14
    HerausforderungEin Präfix mit ungültigem Rest zurückweisen
  15. 15
    ZusammenfassungLesen und Interpretation getrennt halten
3

Bytes übertragen und ihre Bedeutung festlegen

12 Schritte
Einen unvollständigen letzten Block korrekt kopieren und anschließend Datensätze anhand eines ausdrücklich festgelegten Binäraufbaus dekodieren.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungBytes von Werten unterscheiden
  2. 2
    ErklärungFestlegen, was der Rückgabewert zählt
  3. 3
    BeispielDen letzten unvollständigen Block verfolgen
  4. 4
    KernaussageEinen Datensatz von einem Byteblock unterscheiden
  5. 5
    HerausforderungDie tatsächlich gelesene Anzahl verwenden
  6. 6
    ErklärungDas Format nicht vom Compiler festlegen lassen
  7. 7
    BeispielEinen Little-Endian-Wert dekodieren
  8. 8
    BeispielEinen Datensatz anhand seines Aufbaus finden
  9. 9
    HerausforderungDie angegebene Datenmenge prüfen
  10. 10
    VerbindungDer vom Format erlaubten Struktur folgen
  11. 11
    HerausforderungVor dem Dekodieren ein vollständiges Feld verlangen
  12. 12
    ZusammenfassungÜbertragung und Format aufeinander abstimmen
4

Einen Datensatz finden und sicher ersetzen

13 Schritte
Einen Versatz berechnen, eine gespeicherte Position bewahren und beim Ersetzen eines Datensatzes die Regeln für Datenströme im Aktualisierungsmodus beachten.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungZu den benötigten Daten springen
  2. 2
    ErklärungEinen Bezugspunkt wählen
  3. 3
    BeispielDen Datensatz mit Index 3 finden
  4. 4
    TippPositionen in Textströmen als Positionsangaben wiederherstellen
  5. 5
    HerausforderungDen dritten Datensatz finden
  6. 6
    ErklärungDie vorhandene Datei erhalten
  7. 7
    BeispielDen Anfang des Datensatzes wiederherstellen
  8. 8
    KernaussageDen Ersatz abschließen
  9. 9
    HerausforderungSowohl Modus als auch Positionierung wählen
  10. 10
    VerbindungBei einem wachsenden Datensatz neu schreiben
  11. 11
    TippDas Einsetzen wiederherstellbar halten
  12. 12
    HerausforderungEinen abgeschlossenen Anhängevorgang interpretieren
  13. 13
    ZusammenfassungPositionierung gehört zu einer zulässigen Aktualisierung
5

Fehler bei Meldung und Ressourcenfreigabe berücksichtigen

12 Schritte
Das Ende der Eingabe untersuchen, den ursprünglichen Fehler bewahren und späte Ausgabefehler im endgültigen Ergebnis berücksichtigen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungZum Erfolg gehört das abschließende Schließen
  2. 2
    ErklärungJedes Funktionsergebnis lesen
  3. 3
    ErklärungErklären, warum die Eingabe endete
  4. 4
    HerausforderungEinen abgeschnittenen Dateikopf zurückweisen
  5. 5
    ErklärungDen ursprünglichen Grund festhalten
  6. 6
    BeispielEine Meldung aus dem gespeicherten Code ausgeben
  7. 7
    HerausforderungDie richtige Quelle für die Diagnose wählen
  8. 8
    ErklärungFesthalten, welche Datenströme geöffnet wurden
  9. 9
    BeispielDen Erfolgsstatus bei der Ressourcenfreigabe ändern
  10. 10
    HerausforderungDie nächste Aktion eines Skripts bestimmen
  11. 11
    KernaussageDas Original nach einem Umbenennungsfehler behalten
  12. 12
    ZusammenfassungDas Ergebnis der gesamten Aufgabe zurückgeben

Dateiein- und -ausgabe in C: Übungsaufgaben

40 Übungsfragen in 4 Sets

Set 1: Datenströme wählen und vollständige Eingaben verarbeiten10 Übungsfragen

Frage 1 von 10

Welcher Modus liest eine komprimierte Datei ohne Textumwandlung?
  1. "r"
  2. "rb"
  3. "wb"
  4. "ab"
Antwort zeigenAntwort ausblenden
Antwort: B
"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

Ein Programm muss einem vorhandenen Protokoll einen neuen Eintrag hinzufügen und frühere Einträge erhalten. Welcher Öffnungsmodus passt?
  1. "w"
  2. "r"
  3. "w+"
  4. "a"
Antwort zeigenAntwort ausblenden
Antwort: D
Der Anhängemodus erhält vorhandene Inhalte und platziert jeden Schreibvorgang am Ende. Der Modus "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?
  1. "mapl"
  2. "maple"
  3. "maple\n"
  4. "map"
Antwort zeigenAntwort ausblenden
Antwort: A
Der Code ersetzt immer das letzte gespeicherte Zeichen und entfernt daher das e aus "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

Auf einem System mit vorzeichenbehaftetem 8-Bit-char beginnt eine Binärdatei mit dem Byte 0xFF und enthält weitere Daten. Warum kann char c; while ((c = fgetc(fp)) != EOF) sofort anhalten?
  1. Der Binärmodus wandelt 0xFF in das Dateiende um.
  2. Die Zuweisung vergleicht, bevor sie das Byte speichert.
  3. Das Byte kann bei der Umwandlung in char zu -1 werden.
  4. Der erste erfolgreiche Lesevorgang setzt den EOF-Indikator.
Antwort zeigenAntwort ausblenden
Antwort: C
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

Gegeben ist char tag[14]; int count;. Welcher Aufruf liest sicher ein Wort und eine Ganzzahl in diese Objekte?
  1. fscanf(fp, "%14s %d", tag, &count)
  2. fscanf(fp, "%13s %d", tag, &count)
  3. fscanf(fp, "%13s %d", tag, count)
  4. fscanf(fp, "%14s %d", tag, count)
Antwort zeigenAntwort ausblenden
Antwort: B
Die Wortkonvertierung darf höchstens 13 Zeichen zulassen, damit im 14 Byte großen Array ein Byte für das abschließende Nullzeichen bleibt. Die Ganzzahlkonvertierung benötigt außerdem &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

Ein Bericht wird in eine Datei umgeleitet. Über welchen Datenstrom sollte eine Meldung ausgegeben werden, dass ein Eingabedatensatz ungültig ist?
  1. stderr
  2. stdout
  3. stdin
  4. Über den Ausgabestrom des Berichts
Antwort zeigenAntwort ausblenden
Antwort: A
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

Ein erfolgreiches fwrite(a, 4, 7, fp) schreibt jedes angeforderte Element. Was gibt der Aufruf zurück?
  1. 4
  2. 28
  3. 7
  4. 1
Antwort zeigenAntwort ausblenden
Antwort: C
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

Ein 32-Bit-Feld in Little-Endian-Reihenfolge enthält die 8-Bit-Bytes 07 02 00 00. Welchen Wert stellt es dar?
  1. 1794
  2. 7
  3. 131079
  4. 519
Antwort zeigenAntwort ausblenden
Antwort: D
In Little-Endian-Reihenfolge hat das erste Byte das Gewicht 1 und das zweite das Gewicht 256. Das ergibt 7 plus 512, also 519. Würde 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

Eine Binärdatei hat einen 12 Byte großen Dateikopf und 20 Byte große Datensätze. Nach dem Positionieren zu Byte 12 liest ein Programm zwei vollständige Datensätze. Welcher relative fseek-Aufruf wählt nun den vierten Datensatz, ab 1 gezählt?
  1. fseek(fp, 40L, SEEK_CUR)
  2. fseek(fp, 60L, SEEK_CUR)
  3. fseek(fp, 20L, SEEK_CUR)
  4. fseek(fp, 72L, SEEK_CUR)
Antwort zeigenAntwort ausblenden
Antwort: C
Die beiden Lesevorgänge verschieben die Position von Byte 12 zu Byte 52. Der vierte Datensatz hat Index 3; sein Anfang liegt daher nach dem Dateikopf und drei früheren Datensätzen bei Byte 72. Eine Verschiebung um 20 Byte nach vorn erreicht ihn. Die Übergabe von 72 mit SEEK_CUR würde diese absolute Position zur aktuellen Position addieren, statt sie auszuwählen.

Frage 10 von 10

Ein erforderlicher 16 Byte großer Dateikopf wird mit 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?
  1. Den unvollständigen Dateikopf zurückweisen, ohne seine Felder zu interpretieren.
  2. Den Dateikopf interpretieren, weil kein Datenstromfehler auftrat.
  3. Den Dateikopf erneut lesen, ohne den Datenstromzustand zu ändern.
  4. Die Indikatoren zurücksetzen und die mit Nullen gefüllten Felder annehmen.
Antwort zeigenAntwort ausblenden
Antwort: A
Es kam kein vollständiger Dateikopf an, und die Indikatoren zeigen das Dateiende statt eines Zugriffsfehlers im Datenstrom. Der Datei fehlen daher erforderliche Formatdaten, obwohl der Lesevorgang nicht durch einen Zugriffsfehler fehlschlug. Das Objekt kann ein unvollständiges Element mit unbestimmtem Wert enthalten; das Fehlen von 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

Ein Programm wird in /work/run gestartet und öffnet "data/items.txt". Welcher Pfad wird aufgelöst?
  1. /work/data/items.txt
  2. /work/run/items.txt
  3. /work/run/data/items.txt
  4. Der Pfad neben der Quelldatei
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Wohin geht beim Shell-Befehl producer | consumer > result.txt die Standardfehlerausgabe des ersten Programms standardmäßig?
  1. An die Standardeingabe des zweiten Programms
  2. An ihr geerbtes Fehlerziel
  3. Zusammen mit den Ergebnissen nach result.txt
  4. An die Standardeingabe des ersten Programms
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine 1.540 Byte große Datei wird mit einem 600 Byte großen Puffer und fread(buf, 1, 600, in) kopiert. Welche Größen sollten die erfolgreichen Schreibvorgänge haben?
  1. 600, 600, 340
  2. 600, 600, 600
  3. 600, 340, 600
  4. 600, 600, 0
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?
  1. Das Ziel enthält jetzt den vollständigen Namen.
  2. Der Name mit neun Zeichen passt in den Puffer.
  3. Neun gespeicherte Bytes enthalten das Nullzeichen.
  4. Das Ergebnis wurde abgeschnitten und muss behandelt werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Ein Prüfprogramm erlaubt höchstens sieben Inhaltszeichen pro Zeile. Eine fehlerfreie Datei enthält "cedars\nmaplesx". Das Programm ruft wiederholt fgets(buf, 8, fp) auf. Welche Datensatzgrenzen muss es erkennen?
  1. Einen gültigen Datensatz aus beiden zusammengefügten Teilen.
  2. Einen gültigen Datensatz: „cedars“; „maplesx“ zurückweisen.
  3. Zwei gültige Datensätze: „cedars“ und „maples“.
  4. Zwei gültige Datensätze: „cedars“ und „maplesx“.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Eine fehlerfreie Eingabe enthält "north\nsouth\neast". Wie viele Zeilen und Zeilenumbruchzeichen enthält sie?
  1. 3 Zeilen und 3 Zeilenumbrüche
  2. 2 Zeilen und 2 Zeilenumbrüche
  3. 3 Zeilen und 2 Zeilenumbrüche
  4. 2 Zeilen und 3 Zeilenumbrüche
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Zwei Systeme stellen beide 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?
  1. Definierte Felder einzeln in einer gewählten Byte-Reihenfolge kodieren.
  2. Die Komponente durch ein einfaches unsigned int ersetzen.
  3. sizeof in den Dateikopf schreiben und die rohe Ausgabe beibehalten.
  4. Die Struktur beibehalten und die Datei mit "wb" öffnen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Die Position eines Textstroms wurde mit long p = ftell(fp); gespeichert und geprüft. Welcher zulässige Aufruf kehrt nach weiterem Lesen dorthin zurück?
  1. fseek(fp, p, SEEK_CUR)
  2. fseek(fp, p, SEEK_SET)
  3. fseek(fp, p + 1, SEEK_SET)
  4. fseek(fp, -p, SEEK_END)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Binärdatei hat einen 8 Byte großen Dateikopf und fünf 16 Byte große Datensätze. Ein "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?
  1. fseek(fp, -16L, SEEK_CUR), verschiebt die Position zu Byte 40.
  2. fseek(fp, 0L, SEEK_CUR), belässt die Position bei Byte 56.
  3. fseek(fp, 24L, SEEK_SET), verschiebt die Position zu Byte 24.
  4. fflush(fp), belässt die Position bei Byte 56.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein fehlgeschlagener Bibliotheksaufruf setzt errno. Das Programm muss einen anderen Datenstrom schließen, bevor es die ursprüngliche Ursache meldet. Was sollte es zuerst speichern?
  1. Die aktuelle Länge des Ausgabepuffers
  2. Den nächsten verfügbaren Datenstromzeiger
  3. Den aktuellen Wert von errno
  4. Das Ergebnis eines neuen erfolgreichen Aufrufs
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

Unter Windows mit 8-Bit-Bytes wird "x\ny\n" im Textmodus geschrieben. Wie viele gespeicherte Bytes ergeben sich?
  1. 4
  2. 6
  3. 8
  4. 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein C11-Programm muss einen neuen binären Export erstellen, aber eine vorhandene Datei dieses Namens erhalten. Welcher Modus passt?
  1. "wb"
  2. "ab"
  3. "r+b"
  4. "wbx"
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Ein Format hat einen 8 Byte großen Dateikopf und genau 18 Byte große Datensätze. Sein Anzahlfeld gibt 6 Datensätze an, aber die vollständige Datei ist nur 98 Byte groß. Was sollte ein Leseprogramm daraus schließen, bevor es Speicher für diese Datensätze reserviert?
  1. Die Datei kann die sechs angegebenen Datensätze nicht liefern.
  2. Die Anzahl ist gültig, weil der Dateikopf vollständig ist.
  3. Die Datei enthält nach Runden der Länge sechs Datensätze.
  4. Der letzte Datensatz kann mit Nullbytes ergänzt werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine vollständige Zeile "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?
  1. Annehmen, weil errno null geblieben ist.
  2. Nach dem Verwerfen der beiden Buchstaben annehmen.
  3. Zurückweisen, weil Text außer Leerraumzeichen verbleibt.
  4. Zurückweisen, weil kein numerisches Präfix gefunden wurde.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine fehlerfreie Binärdatei hat 27 Byte. Zwei Aufrufe verwenden 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?
  1. 11; seine elf Bytes bilden ein vollständiges Element.
  2. 1; die fehlenden fünf Bytes werden mit Nullen gefüllt.
  3. 0; sein unvollständiger Wert kann als nächster Datensatz dienen.
  4. 0; das unvollständige Element darf nicht interpretiert werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Die Eingabe lautet "18 pear 26". Nach einem erfolgreichen fscanf(fp, "%d", &n) gibt derselbe Aufruf zweimal 0 zurück. Warum hat er 26 nicht erreicht?
  1. Der erste Lesevorgang hat automatisch alle drei Felder verbraucht.
  2. Das nicht passende Wort bleibt bei der nächsten Konvertierung ungelesen.
  3. Der zweite Lesevorgang erreichte EOF und entfernte das Wort.
  4. Das Ganzzahlziel muss in einen double-Wert geändert werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Welcher Wert zeigt an, dass fseek fehlgeschlagen ist?
  1. Ein Ergebnis ungleich null
  2. Der neue Byteversatz
  3. Immer genau EOF
  4. Immer genau -1L
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine positionierbare Binärdatei enthält einen 10 Byte großen Dateikopf und vier 14 Byte große Datensätze. Ein Leseprogramm positioniert in einem "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?
  1. fflush(fp)
  2. fseek(fp, 0L, SEEK_CUR)
  3. fseek(fp, 38L, SEEK_SET)
  4. fseek(fp, 52L, SEEK_SET)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Alle Schreibvorgänge haben die erwarteten Anzahlen zurückgegeben, aber fclose(out) gibt EOF zurück. Welches Ergebnis sollte das Programm melden?
  1. Erfolg, weil alle Daten den Schreibpuffer erreicht haben.
  2. Erfolg nach einem erneuten Aufruf von fclose für denselben Datenstrom.
  3. Fehler nur, wenn auch feof(out) gesetzt ist.
  4. Fehler, weil gepufferte Ausgabe unvollständig sein kann.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Die Eingabe wird erfolgreich geöffnet, danach gibt das Öffnen der Ausgabe NULL zurück. Was sollte dieser Programmpfad tun, bevor er einen Fehler zurückgibt?
  1. Die Eingabe einmal schließen und den Fehler beim Öffnen der Ausgabe melden.
  2. Beide Zeiger schließen, einschließlich des Nullzeigers für die Ausgabe.
  3. Den Ausgabepfad entfernen, obwohl er nicht geöffnet wurde.
  4. Sofort zurückkehren und die Eingabe für den Aufrufer offen lassen.
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

Ein Programm liest unter Linux eine unter Windows erstellte Zeile und entfernt nur deren LF. Der Vergleich des Ergebnisses mit "ready" schlägt fehl, obwohl die sichtbaren Buchstaben übereinstimmen. Welches erhaltene Zeichen erklärt das?
  1. Ein Tabulator vor den Buchstaben
  2. Ein CR vor dem entfernten LF
  3. Ein zweites abschließendes Nullzeichen
  4. Ein von fgets gespeichertes EOF-Byte
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein Programm soll seine normale Ausgabe in der Shell an eine Datei namens totals.txt anhängen. Welcher Umleitungsoperator wird benötigt?
  1. < totals.txt
  2. 2> totals.txt
  3. > totals.txt
  4. >> totals.txt
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine Datei hat drei 12 Byte große Datensätze mit den Punkteständen 10, 20 und 30. Im Modus "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?
  1. Zweiter: 20; letzter: 25; insgesamt vier Datensätze.
  2. Zweiter: 25; letzter: 30; insgesamt drei Datensätze.
  3. Zweiter: 25; letzter: 25; insgesamt vier Datensätze.
  4. Zweiter: 20; letzter: 30; insgesamt drei Datensätze.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Datensätze für vollständige Ganzzahlen dürfen höchstens sieben Inhaltszeichen enthalten. Bei 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?
  1. Beide vollständigen numerischen Präfixe annehmen.
  2. Beide als zu lang für den Puffer zurückweisen.
  3. Den ersten zurückweisen; den zweiten annehmen.
  4. Nur den ersten annehmen, der mit LF endet.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Ersatzdatei ist vollständig geschrieben und geschlossen. Das Umbenennen des Originals in eine Sicherung gelingt; das Einsetzen der Ersatzdatei schlägt fehl, weil ihr Ziel inzwischen belegt ist. Welche Handlung erhält das Original, ohne die neue Datei am Ziel zu überschreiben?
  1. Die Sicherung löschen, die Ersatzdatei behalten und einen Fehler zurückgeben.
  2. Sicherung und Ersatzdatei behalten, einen Fehler melden und den Namenskonflikt vor einem erneuten Versuch lösen.
  3. Die Sicherung auf den belegten Namen umbenennen, ohne das Ergebnis zu prüfen.
  4. Das belegte Ziel sofort entfernen und das Einsetzen erneut versuchen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Eine fehlerfreie Datei enthält "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?
  1. n = 4, lines = 0; feof vor dem Lesen prüfen und die letzte Zeile zählen.
  2. n = 3, lines = 1; die Klammern der Zuweisung korrigieren, ohne die letzte Zeile zu behandeln.
  3. n = 3, lines = 0; die Klammern der Zuweisung korrigieren und die letzte Zeile ohne Zeilenumbruch zählen.
  4. n = 2, lines = 1; die Bedingung behalten und c von int in char ändern.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein Lesevorgang mit Elementgröße 1 fordert einen erforderlichen 12 Byte großen Dateikopf an, erhält aber 9 Byte. feof ist gesetzt und ferror ist nicht gesetzt. Danach folgt ein erfolgreiches fseek zum Anfang. Was bleibt richtig und was muss der Parser tun?
  1. EOF ist zurückgesetzt; den unvollständigen Dateikopf zurückweisen, sofern kein vollständiger erneuter Versuch gelingt.
  2. EOF bleibt gesetzt; beide Indikatoren zurücksetzen und die neun Bytes dekodieren.
  3. Der Dateikopf ist vollständig geworden; ihn nach erneuter Prüfung von feof dekodieren.
  4. Der Fehlerindikator ist jetzt gesetzt; einen neuen Positionierungsfehler melden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Das Öffnen einer Eingabe schlägt fehl und ihr errno wird als open_error gespeichert. Das anschließende Schließen einer zuvor geöffneten Ausgabe schlägt ebenfalls fehl und liefert einen anderen Code, der als close_error gespeichert wird. Wie sollte eine Diagnosemeldung beide Fehler festhalten?
  1. Beide Vorgänge mit strerror(close_error) beschreiben; der neueste Code ersetzt frühere Ursachen.
  2. Das Öffnen nach der Ressourcenfreigabe mit perror beschreiben und die Meldung zum Schließen als überflüssig unterdrücken.
  3. Den gespeicherten Öffnungsfehler beschreiben, aber Erfolg zurückgeben, wenn die Ressourcenfreigabe ihre letzte Anweisung erreicht hat.
  4. Das Öffnen mit strerror(open_error) und das Schließen mit strerror(close_error) beschreiben; einen Fehler zurückgeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

fopen gibt einen gültigen Datenstrom zurück, aber errno enthält weiterhin einen früheren Wert ungleich null. Was belegt dieser Wert über das Öffnen?
  1. Der Datenstrom muss wegen eines fehlgeschlagenen Öffnens geschlossen werden.
  2. Er belegt keinen Fehler bei diesem erfolgreichen Öffnen.
  3. Der nächste Lesevorgang wird garantiert sofort fehlschlagen.
  4. Die Datei wurde mit einem falschen Zugriffsmodus geöffnet.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein Kopierprogramm hat unterschiedliche Ein- und Ausgabedatenströme geöffnet. Seine Eingabe meldet einen Lesefehler; bei der Ressourcenfreigabe wird dann die Ausgabe geschlossen, was EOF zurückgibt. Die Eingabe ist noch offen und die Ausgabe unvollständig. Welche verbleibenden Schritte und welches Ergebnis schützen die Quelle und geben Ressourcen frei?
  1. Das Schließen der Ausgabe erneut versuchen, die Eingabe einmal schließen, unvollständige Ausgabe entfernen, die Quelle erhalten und einen Fehler zurückgeben.
  2. Die Eingabe einmal schließen, unvollständige Ausgabe entfernen, die Quelle erhalten und bei erfolgreicher Ressourcenfreigabe Erfolg zurückgeben.
  3. Den Lesefehler zurücksetzen, die Eingabe einmal schließen, beide Dateien behalten und Erfolg zurückgeben, wenn das letzte Schließen gelingt.
  4. Die Eingabe einmal schließen, unvollständige Ausgabe mit Prüfung zu entfernen versuchen, die Quelle erhalten und einen Fehler zurückgeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

61 Schlüsselbegriffe, jeweils mit Definition

A

abschließendes Nullzeichen
Ein abschließendes Nullzeichen ist das Zeichen mit dem Wert null, das das Ende einer C-Zeichenkette markiert, ohne zu ihrem sichtbaren Text zu zählen.
Ein Puffer benötigt zusätzlich zu den Zeichen, die fgets lesen darf, ein weiteres Byte.
aktuelles Arbeitsverzeichnis
Das aktuelle Arbeitsverzeichnis ist der Bezugsordner des laufenden Programms zum Auflösen relativer Pfadnamen.
Wird ein Programm in einem anderen Ordner gestartet, ändert sich die durch "data/input.txt" bezeichnete Datei.
Anhängemodus
Der Anhängemodus ist ein Zugriff, der jeden Ausgabevorgang an das aktuelle Dateiende zwingt, unabhängig von früheren Positionierungen.
Das Positionieren zu Byte null in einem "a+b"-Datenstrom kann die Eingabeposition wählen, aber den nächsten Schreibvorgang nicht umleiten.
Aufräumpfad
Ein Aufräumpfad ist der Weg im Kontrollfluss, der Ressourcen freigibt, die vor der Feststellung von Erfolg oder Fehler angefordert wurden.
Eine Marke kann nach mehreren unterschiedlichen Verarbeitungsfehlern die geöffneten Datenströme schließen.
ausfallsicherer Dateiaustausch
Ein ausfallsicherer Dateiaustausch hält eine vorhandene Datei verfügbar, bis eine fertiggestellte neue Version erfolgreich eingesetzt wurde.
Schlägt das Umbenennen zum Einsetzen der neuen Version fehl, bewahre die Sicherung auf und melde ihren Speicherort zur Wiederherstellung.

B

Besitz von Ressourcen
Der Besitz von Ressourcen legt fest, welcher Teil eines Programms für die Freigabe eines Datenstroms oder einer anderen angeforderten Ressource verantwortlich ist.
Eine Hilfsfunktion schließt einen von ihr geöffneten Datenstrom und überlässt einen geliehenen Datenstrom ihrem Aufrufer.
Big-Endian-Reihenfolge
Die Big-Endian-Reihenfolge platziert das höchstwertige Byte eines Werts aus mehreren Bytes zuerst in der Bytefolge.
Der Wert 258 erscheint in einem 32-Bit-Feld in Big-Endian-Reihenfolge als 00 00 01 02.
Binärmodus
Der Binärmodus ist ein Datenstromzugriff, der Bytewerte ohne die für Text verwendeten Zeichenumwandlungen erhält.
Öffne ein Bild mit "rb", damit die Textumwandlung seine komprimierten Bytes nicht verändern kann.
blockweises Kopieren
Blockweises Kopieren überträgt eine Datei durch einen wiederverwendbaren Puffer und schreibt nur die Bytes, die jeder erfolgreiche Lesevorgang geliefert hat.
Ein unvollständiger letzter Lesevorgang muss zu einem entsprechend kurzen letzten Schreibvorgang führen, damit keine übrig gebliebenen Pufferdaten hinzugefügt werden.
Byte-Reihenfolge
Die Byte-Reihenfolge ist die Reihenfolge, in der die Bytes eines Werts aus mehreren Bytes im Speicher oder in einer Datei angeordnet sind.
Ein Dateiformat legt seine Byte-Reihenfolge unabhängig vom lesenden Rechner fest.

C

clearerr
clearerr setzt sowohl den Dateiende- als auch den Fehlerindikator eines Datenstroms zurück, ohne die Ursache eines Fehlers zu beheben.
Nach dem Beheben eines Eingabeproblems kann ein Programm vor einem kontrollierten erneuten Versuch die Indikatoren zurücksetzen.

D

Dateiendeindikator
Der Dateiendeindikator hält fest, dass ein früherer Eingabevorgang auf das Ende des Datenstroms gestoßen ist.
Er wird nach dem Lesen geprüft; eine vorherige Prüfung kann nicht vorhersagen, ob das nächste Byte existiert.
Dateikopf
Ein Dateikopf ist ein Anfangsteil einer Datei mit Identifikations- und Strukturfeldern, die zur Interpretation der folgenden Daten benötigt werden.
Prüfe die vollständige Signatur und Anzahl, bevor du den durch einen Dateikopf angegebenen Datensätzen vertraust.
Dateiposition
Die Dateiposition ist die Stelle in einem Datenstrom, an der der nächste Lese- oder Schreibvorgang beginnt, sofern der Datenstrom die Positionierung unterstützt.
Das Lesen eines 24 Byte großen binären Datensatzes verschiebt die Position über alle 24 Bytes hinaus.
Dateisignatur
Eine Dateisignatur ist eine festgelegte Folge von Bytes am Anfang einer Datei, die einem Leseprogramm hilft, ein Dateiformat oder eine Version zu erkennen.
Die JPEG-Startmarkierung hilft, mögliche Bilder zu finden, bevor die übrige Struktur geprüft wird.
Datenstrom
Ein Datenstrom ist ein Kanal der Standardbibliothek, der ein Programm mit einer Folge von Eingabe- oder Ausgabezeichen verbindet.
Ein Leseprogramm kann eine Datei auf einem Datenträger oder eine Terminaleingabe über dieselbe Datenstromschnittstelle verarbeiten.
Datenstrom im Aktualisierungsmodus
Ein Datenstrom im Aktualisierungsmodus ist ein mit einem +-Modus geöffneter Datenstrom, bei dem Ein- und Ausgabe unter Einhaltung von Wechselregeln erlaubt sind.
Das Öffnen einer vorhandenen binären Datensatzdatei mit "r+b" erhält sie für den Lese- und Schreibzugriff.
Direktzugriff
Direktzugriff ist das unmittelbare Wechseln zu einer gewählten Stelle in einer positionierbaren Datei, ohne alle vorherigen Daten zu lesen.
Ein Dateikopf und eine feste Datensatzbreite ermöglichen es einem Programm zu berechnen, wo ein ausgewählter Datensatz beginnt.

E

Endzeiger
Ein Endzeiger von strtol markiert das erste Zeichen nach dem numerischen Präfix, das die Konvertierung akzeptiert hat.
Bei "86cm" zeigt der Zeiger auf das c, sodass ein Ganzzahlfeld den Rest zurückweisen kann.
EOF
EOF ist eine negative Ganzzahlkonstante, die Zeicheneingabefunktionen verwenden, wenn die Eingabe endet oder ein Lesefehler auftritt.
Speichere den Rückgabewert in einem int, damit ein tatsächliches Byte nicht mit dieser Markierung verwechselt wird.
ERANGE
ERANGE ist der standardmäßige Wertebereichsfehlercode, der verwendet wird, wenn eine Konvertierung oder Berechnung einen Wert außerhalb ihres unterstützten Bereichs ergibt.
Setze errno vor strtol zurück, damit ein alter Wertebereichsfehler nicht mit einem neuen verwechselt werden kann.
errno
errno ist ein veränderbarer ganzzahliger Fehlercodeausdruck, dessen Bedeutung vom dokumentierten Fehlerverhalten einer Funktion abhängt.
Speichere seinen Wert unmittelbar nach einem gemeldeten Fehler, bevor ein anderer Bibliotheksaufruf ihn ändern kann.
exklusives Erstellen
Exklusives Erstellen bedeutet, eine neue Datei nur zu öffnen, wenn ihr Name nicht bereits eine vorhandene Datei bezeichnet.
Der C11-Modus "wx" schützt einen vorhandenen Bericht davor, beim Öffnen gelöscht zu werden.

F

fclose
fclose ist die Funktion, die ausstehende Ausgabe überträgt, einen Datenstrom schließt und Erfolg mit 0 oder einen Fehler mit EOF meldet.
Nach der Rückgabe von fclose(fp) kann dieser Datenstrom nicht erneut verwendet werden, auch nicht nach einem Fehler.
Fehlerindikator
Der Fehlerindikator hält einen Lese- oder Schreibfehler eines Datenstroms fest und bleibt gesetzt, bis er ausdrücklich zurückgesetzt wird.
Nachdem fgets NULL zurückgegeben hat, kann ferror einen fehlgeschlagenen Zugriff vom normalen Ende unterscheiden.
Feldbreite
Die Feldbreite steuert je nach Funktion der formatierten Ein- und Ausgabe eine minimale Ausgabebreite oder eine maximale Eingabebreite.
Die Eingabe %23s lässt in einem 24 Byte großen Zeichenarray Platz für ein abschließendes Nullzeichen.
fgetc
fgetc ist die Funktion, die ein Zeichen als in int umgewandelten vorzeichenlosen Zeichenwert liest oder EOF zurückgibt.
Eine Schleife zählt ein Trennzeichen erst, nachdem fgetc erfolgreich ein Zeichen zurückgegeben hat.
fgets
fgets liest eine begrenzte Zeichenfolge, behält einen gefundenen Zeilenumbruch und fügt bei Erfolg ein abschließendes Nullzeichen hinzu.
Eine Zeile, die länger als ein 32 Byte großer Puffer ist, wird über mehrere erfolgreiche Lesevorgänge zurückgegeben.
FILE-Zeiger
Ein FILE-Zeiger ist ein Wert vom Typ FILE *, der Bibliotheksfunktionen Zugriff auf einen Datenstrom und dessen gespeicherten Zustand gibt.
Übergib fp an fgetc, nachdem du geprüft hast, dass das Öffnen einen gültigen Datenstromzeiger zurückgegeben hat.
fopen
fopen ist die Funktion der Standardbibliothek, die eine benannte Datei in einem gewählten Modus öffnet und einen Datenstromzeiger oder NULL zurückgibt.
Ein fehlgeschlagenes Öffnen zum Lesen hinterlässt keinen gültigen Datenstrom für einen späteren Aufruf von fgetc oder fclose.
fpos_t
fpos_t ist der undurchsichtige Typ, den fgetpos und fsetpos zum Speichern und Wiederherstellen aller erforderlichen Informationen über die Dateiposition verwenden.
Eine gespeicherte Positionsangabe wird über die Bibliotheksfunktionen wiederhergestellt, ohne mit ihrem Wert zu rechnen.
fputc
fputc ist die Funktion, die ein Zeichen in einen Datenstrom schreibt und diesen Zeichenwert oder bei einem Fehler EOF zurückgibt.
Ein Filter für Großbuchstaben prüft jeden Aufruf von fputc, nachdem er das gelesene Zeichen umgewandelt hat.
fread
fread überträgt Eingabebytes in den Speicher und gibt für die angeforderte Größe und Anzahl die Anzahl der vollständig erhaltenen Elemente zurück.
Ein unvollständiger Lesevorgang eines Datensatzes muss geprüft werden, bevor eines seiner Felder interpretiert wird.
fseek
fseek fordert eine neue Dateiposition relativ zum Anfang, zur aktuellen Position oder zum Ende an und gibt bei einem Fehler einen Wert ungleich null zurück.
Ein Programm prüft die Positionierung, bevor es den am angeforderten Versatz erwarteten Datensatz liest.
ftell
ftell meldet die Position eines Datenstroms als long und gibt bei einem Fehler -1L zurück; eine Position in einem Textstrom muss als gespeicherte Stelle behandelt werden.
Ein Leseprogramm für Binärdatensätze kann seine Position prüfen, nachdem es einen vollständigen Datensatz fester Größe gelesen hat.
Füllbytes in Strukturen
Füllbytes in Strukturen sind zusätzliche Bytes, die ein Compiler zur Speicherausrichtung zwischen Komponenten oder nach der letzten Komponente einfügen kann.
Ein char, gefolgt von einem int, kann acht Byte belegen, auch wenn sich ihre Größen zu fünf addieren.
fwrite
fwrite überträgt Objektbytes aus dem Speicher in einen Datenstrom und gibt die Anzahl der vollständig geschriebenen Elemente zurück.
Zum Schreiben eines Arrays werden die Adresse seiner Daten, die Elementgröße und die beabsichtigte Elementanzahl benötigt.

G

Ganzzahltyp mit exakter Bitbreite
Ein Ganzzahltyp mit exakter Bitbreite hat, wo er verfügbar ist, eine genau festgelegte Bitbreite, ohne eine externe Byte-Reihenfolge zu bestimmen.
Ein uint32_t-Feld benötigt weiterhin eine ausdrückliche Bytekodierung, damit eine Austauschdatei portabel ist.

I

In-place-Aktualisierung
Eine In-place-Aktualisierung ersetzt Daten innerhalb ihres vorhandenen Dateibereichs, ohne nachfolgende Datensätze zu verschieben oder neu zu schreiben.
Ein geänderter Datensatz fester Größe muss an seine ursprüngliche Anfangsposition zurückgeschrieben werden.

K

Konvertierungsspezifikation
Eine Konvertierungsspezifikation ist eine mit einem Prozentzeichen beginnende Anweisung, die festlegt, wie die formatierte Ein- und Ausgabe einen Wert ausgibt oder konvertiert.
Bei der Eingabe benötigt %lf einen double *, während %f ein Ziel vom Typ float * benötigt.

L

Little-Endian-Reihenfolge
Die Little-Endian-Reihenfolge platziert das niederwertigste Byte eines Werts aus mehreren Bytes zuerst in der Bytefolge.
Die vier Bytes 02 01 00 00 stellen in einem 32-Bit-Feld in Little-Endian-Reihenfolge den Wert 258 dar.

M

Maskierung von CSV-Feldern
Die Maskierung von CSV-Feldern schließt Textfelder mit Kommas, Anführungszeichen oder Zeilenumbrüchen in Anführungszeichen ein und verdoppelt darin enthaltene Anführungszeichen.
Ein Stadtname mit einem Komma muss beim erneuten Lesen des Datensatzes ein Feld bleiben.

Ö

Öffnungsmodus
Ein Öffnungsmodus ist eine Zeichenkette, die fopen angibt, welcher Zugriff erlaubt ist und ob vorhandener Inhalt erhalten oder gelöscht wird.
Die Wahl von "a" erhält frühere Protokolleinträge und platziert jeden neuen Schreibvorgang am Ende.

P

perror
perror schreibt eine Bezeichnung und die Meldung zum aktuellen errno-Wert auf die Standardfehlerausgabe, gefolgt von einem Zeilenumbruch.
Eine Bezeichnung mit dem Dateinamen macht eine Meldung über eine fehlende Datei hilfreicher als eine Meldung ohne Bezeichnung.
Pipe
Eine Pipe verbindet die Standardausgabe eines Programms als Datenfluss mit der Standardeingabe eines anderen Programms.
Ein erzeugendes Programm kann ein Sortierprogramm direkt mit Daten versorgen, ohne zuerst eine Zwischendatei zu speichern.
Pufferung
Pufferung ist das vorübergehende Sammeln von Daten im Speicher, damit viele kleine Ein- und Ausgabeanforderungen in größeren Übertragungen zusammengefasst werden können.
Ein Bericht kann im Ausgabepuffer bleiben, bis der Puffer geleert oder der Datenstrom geschlossen wird.

R

rename
rename ändert den Namen einer Datei und gibt bei Erfolg 0 zurück; die Behandlung eines vorhandenen Ziels hängt von der Implementierung ab.
Halte eine Sicherung verfügbar, falls das Einsetzen einer fertigen Ersatzdatei unter dem Originalnamen fehlschlägt.
Rückgabestatus
Ein Rückgabestatus ist das Ergebnis, das ein Programm an seine Umgebung zurückgibt, damit Skripte eine erfolgreiche von einer fehlgeschlagenen Ausführung unterscheiden können.
Ein abhängiger Befehl nach && darf nach einer unvollständigen Dateikopie nicht ausgeführt werden.

S

snprintf
snprintf formatiert Text in ein Array begrenzter Größe und gibt die vollständige erforderliche Länge ohne das abschließende Nullzeichen oder bei einem Fehler einen negativen Wert zurück.
Ein Rückgabewert, der der Kapazität entspricht, zeigt bereits an, dass der vollständige Text nicht hineingepasst hat.
Standardausgabe
Die Standardausgabe ist der bereits geöffnete Datenstrom stdout für die normalen Ergebnisse und sonstigen hauptsächlichen Ausgaben eines Programms.
Eine Shell-Pipe sendet stdout eines Konvertierungsprogramms an die Standardeingabe eines anderen Programms.
Standardeingabe
Die Standardeingabe ist der bereits geöffnete Datenstrom stdin, aus dem ein Programm normalerweise seine hauptsächliche Eingabe erhält.
Mit < values.txt liest ein Filterprogramm Dateidaten über stdin statt von der Tastatur.
Standardfehlerausgabe
Die Standardfehlerausgabe ist der bereits geöffnete Datenstrom stderr für Diagnosemeldungen, unabhängig vom normalen Ergebnisstrom eines Programms.
Eine fehlgeschlagene Konvertierung kann auf stderr beschrieben werden, während gültige Daten nach stdout gehen.
strcspn
strcspn bestimmt die Länge des Anfangs einer Zeichenkette bis zu einem Zeichen aus einer übergebenen Ausschlussmenge oder die volle Länge, wenn keines vorkommt.
Mit "\r\n" als Ausschlussmenge lässt sich ein Zeilenende finden, ohne gewöhnliche Daten am Ende zu löschen.
strerror
strerror gibt den Meldungstext der Bibliothek für einen übergebenen Fehlercode zurück und ermöglicht es einem Programm, eine eigene Diagnosemeldung zu erstellen.
Verwende einen gespeicherten Code, wenn die Ressourcenfreigabe den aktuellen Wert von errno ersetzt haben könnte.

T

Textmodus
Der Textmodus ist ein Datenstromzugriff für lesbare Zeilen, bei dem die Bibliothek Zeichen entsprechend den Konventionen des Systems umwandeln kann.
Unter Windows wandelt die Textausgabe einen Zeilenumbruch des Programms in die gespeicherte CR-LF-Folge um.

U

Umleitung
Eine Umleitung ist ein Shell-Vorgang, der die Standarddatenströme eines Programms mit Dateien verbindet, ohne dessen Ein- und Ausgabecode zu ändern.
Der Operator 2> kann die Diagnosemeldungen eines Programms in einer separaten Datei speichern.
unvollständiges Element
Ein unvollständiges Element ist eine beim Lesen erhaltene unvollständige Einheit von Objektgröße, die in der Anzahl vollständiger Elemente von fread nicht enthalten ist.
Bei der Eingabe in Datensatzgröße können verbleibende Bytes am Ende nicht als gültiger Datensatz behandelt werden.

Ü

Übereinstimmungsfehler
Ein Übereinstimmungsfehler tritt auf, wenn Eingabezeichen nicht zu einer angeforderten formatierten Konvertierung passen, ohne dass dafür ein Ein- oder Ausgabefehler vorliegen muss.
Das Lesen einer Ganzzahl aus "oak" kann 0 zurückgeben, während Dateiende- und Fehlerindikator des Datenstroms nicht gesetzt sind.

Z

zeilenweises Parsen
Zeilenweises Parsen ist das Interpretieren der Felder einer vollständigen Eingabezeile, nachdem diese in den Speicher gelesen wurde.
Eine fehlerhafte Mengenangabe kann mit ihrer Zeilennummer gemeldet werden, bevor der nächste Datensatz gelesen wird.
Zurückstellen von Zeichen
Zurückstellen von Zeichen bedeutet, ein Zeichen in einen Eingabedatenstrom zurückzugeben, sodass der nächste Lesevorgang es erneut erhält.
Ein Parser kann ein Trennzeichen prüfen und es mit ungetc für die nächste Verarbeitungsstufe zurückstellen.
Zuweisungsanzahl
Die Zuweisungsanzahl ist die Anzahl der Zielwerte, die ein formatierter Eingabeaufruf erfolgreich speichert.
Ein mit fscanf gelesener Datensatz aus zwei Feldern ist nur vollständig, wenn die zurückgegebene Zuweisungsanzahl 2 ist.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten