C Datei-E/A

Im vorherigen Kapitel haben wir erklärt, wie die Sprache C Standardeingabe- und Standardausgabegeräte behandelt. In diesem Kapitel wird vorgestellt, wie C-Programmierer Textdateien oder Binärdateien erstellen, öffnen und schließen.

Eine Datei, ob Textdatei oder Binärdatei, repräsentiert eine Folge von Bytes. Die Sprache C bietet nicht nur Top-Level-Funktionen, sondern auch Low-Level-(OS-)Aufrufe zur Behandlung von Dateien auf Speichergeräten. Dieses Kapitel erläutert die wichtigen Aufrufe für die Dateiverwaltung.

Eine Datei öffnen

Sie könnenfopen( )die Funktion verwenden, um eine neue Datei zu erstellen oder eine vorhandene Datei zu öffnen. Dieser Aufruf initialisiert ein Objekt vom TypFILE, ein Objekt vom TypFILEdas alle notwendigen Informationen zur Steuerung des Streams enthält. Nachfolgend finden Sie den Prototyp dieses Funktionsaufrufs:

FILE *fopen( const char *filename, const char *mode );

Hier istfilenameeine Zeichenkette zur Benennung der Datei; der Zugriffsmodusmodekann einen der folgenden Werte annehmen:

ModeBeschreibung
rÖffnet eine vorhandene Textdatei zum Lesen.
wÖffnet eine Textdatei zum Schreiben. Wenn die Datei nicht existiert, wird eine neue Datei erstellt. Hier schreibt Ihr Programm den Inhalt ab dem Anfang der Datei. Wenn die Datei existiert, wird ihr Inhalt gelöscht (d. h. die Dateilänge wird auf 0 gekürzt).
aÖffnet eine Textdatei zum Schreiben im Anhängemodus. Wenn die Datei nicht existiert, wird eine neue Datei erstellt. Hier hängt Ihr Programm Inhalt an den vorhandenen Dateiinhalt an.
r+Öffnet eine Textdatei sowohl zum Lesen als auch zum Schreiben.
w+Öffnet eine Textdatei sowohl zum Lesen als auch zum Schreiben. Wenn die Datei bereits existiert, wird sie auf die Länge null gekürzt; wenn die Datei nicht existiert, wird eine neue Datei erstellt.
a+Öffnet eine Textdatei sowohl zum Lesen als auch zum Schreiben. Wenn die Datei nicht existiert, wird eine neue Datei erstellt. Das Lesen beginnt am Anfang der Datei, während das Schreiben nur im Anhängemodus erfolgen kann.

Wenn Sie mit Binärdateien arbeiten, verwenden Sie die folgenden Zugriffsmodi anstelle der oben genannten Zugriffsmodi:

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

Eine Datei schließen

Um eine Datei zu schließen, verwenden Sie die Funktion fclose( ). Der Prototyp der Funktion lautet wie folgt:

 int fclose( FILE *fp );

Wenn die Datei erfolgreich geschlossen wurde,fclose( )Die Funktion gibt null zurück; wenn beim Schließen der Datei ein Fehler auftritt, gibt die FunktionEOFEOF zurück. Diese Funktion leert tatsächlich die Daten im Puffer, schließt die Datei und gibt den gesamten für die Datei verwendeten Speicher frei. EOF ist in der Header-Datei definiertstdio.hdie Konstante darin.

Die C-Standardbibliothek bietet verschiedene Funktionen zum Lesen und Schreiben von Dateien Zeichen für Zeichen oder in Form von Zeichenketten fester Länge.

In eine Datei schreiben

Das Folgende ist die einfachste Funktion zum Schreiben eines Zeichens in einen Stream:

int fputc( int c, FILE *fp );

Funktionfputc()schreibt den Zeichenwert des Parameters c in den Ausgabestream, auf den fp zeigt. Bei Erfolg gibt sie das geschriebene Zeichen zurück; tritt ein Fehler auf, gibt sieEOFSie können die folgende Funktion verwenden, um eine nullterminierte Zeichenkette in einen Stream zu schreiben:

int fputs( const char *s, FILE *fp );

Funktionfputs()die Zeichenkettesschreibt in den Ausgabestream, auf den fp zeigt. Bei Erfolg gibt sie einen nicht negativen Wert zurück; tritt ein Fehler auf, gibt sieEOFSie können auchint fprintf(FILE *fp,const char *format, ...)Die Funktion schreibt eine Zeichenkette in die Datei. Probieren Sie das folgende Beispiel aus:

Hinweis:Bitte stellen Sie sicher, dass Sie ein nutzbarestmpVerzeichnis haben. Wenn das Verzeichnis nicht existiert, müssen Sie es zuerst auf Ihrem Computer erstellen.

/tmpDies ist im Allgemeinen das temporäre Verzeichnis auf Linux-Systemen. Wenn Sie unter Windows arbeiten, müssen Sie es in ein vorhandenes Verzeichnis in Ihrer lokalen Umgebung ändern, zum Beispiel: C:\tmp、D:\tmpusw.

Beispiel

#include <stdio.h> int main() { FILE *fp = NULL; fp = fopen("/tmp/test.txt", "w+"); fprintf(fp, "Dies ist ein Test für fprintf...\n"); fputs("Dies ist ein Test für fputs...\n", fp); fclose(fp); }

Wenn der obige Code kompiliert und ausgeführt wird, erstellt er eine neue Datei im Verzeichnis /tmptest.txtund verwendet zwei verschiedene Funktionen, um zwei Zeilen zu schreiben. Als Nächstes lesen wir diese Datei.

Aus einer Datei lesen

Das Folgende ist die einfachste Funktion, um ein einzelnes Zeichen aus einer Datei zu lesen:

int fgetc( FILE * fp );

fgetc()Die Funktion liest ein Zeichen aus der Eingabedatei, auf die fp zeigt. Der Rückgabewert ist das gelesene Zeichen; wenn ein Fehler auftritt, gibt sieEOFDie folgende Funktion ermöglicht es Ihnen, eine Zeichenkette aus einem Stream zu lesen:

char *fgets( char *buf, int n, FILE *fp );

Funktionfgets()Sie liest n - 1 Zeichen aus dem Eingabestream, auf den fp zeigt. Sie kopiert die gelesene Zeichenkette in den Pufferbuf, und hängt einnullZeichen, um die Zeichenkette zu beenden.

Wenn diese Funktion auf ein Zeilenumbruchzeichen '\n' oder das Dateiende EOF stößt, bevor das letzte Zeichen gelesen wird, gibt sie nur die gelesenen Zeichen zurück, einschließlich des Zeilenumbruchs. Sie können auchint fscanf(FILE *fp, const char *format, ...)Funktion verwenden, um Zeichenketten aus einer Datei zu lesen, aber sie stoppt das Lesen, wenn sie auf das erste Leerzeichen oder einen Zeilenumbruch trifft.

Beispiel

#include <stdio.h> int main() { FILE *fp = NULL; char buff[255]; fp = fopen("/tmp/test.txt", "r"); fscanf(fp, "%s", buff); printf("1: %s\n", buff ); fgets(buff, 255, (FILE*)fp); printf("2: %s\n", buff ); fgets(buff, 255, (FILE*)fp); printf("3: %s\n", buff ); fclose(fp); }

Wenn der obige Code kompiliert und ausgeführt wird, liest er die im vorherigen Abschnitt erstellte Datei und erzeugt die folgenden Ergebnisse:

1: This
2: is testing for fprintf...

3: This is testing for fputs...

Zuerst,fscanf()Die Methode las nurDies, weil sie danach auf ein Leerzeichen stieß. Als Nächstes rufen Siefgets()um den verbleibenden Teil bis zum Ende der Zeile zu lesen. Zum Schluss rufen Siefgets()auf, wodurch die zweite Zeile vollständig gelesen wird.

Binäre E/A-Funktionen

Die folgenden zwei Funktionen werden für die binäre Eingabe und Ausgabe verwendet:

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

freadundfwritesind die Kernfunktionen in der C-Standardbibliothek zum Lesen und Schreiben binärer Dateiblöcke. Sie arbeiten direkt auf Speicherdatenblöcken, wodurch der Aufwand der Textformatkonvertierung vermieden wird, und eignen sich besonders für das stapelweise Lesen und Schreiben großer Dateien und strukturierter Daten.

Parameterbeschreibung

Die vier Parameter haben in beiden Funktionen die gleiche Bedeutung, aber die Datenflussrichtung ist entgegengesetzt:

Parameter Type Beschreibung fread-Verhalten fwrite-Verhalten
ptr void* / const void* Zeiger auf Speicherpuffer Zieladresse für Lesevorgang (in Speicher schreiben) Datenquelladresse (aus Speicher lesen)
size size_t Anzahl der Bytes pro Datenblock Blockgröße pro Lesevorgang Blockgröße pro Schreibvorgang
count size_t Anzahl der zu lesenden oder zu schreibenden Datenblöcke Erwartete Anzahl der zu lesenden Blöcke Erwartete Anzahl der zu schreibenden Blöcke
stream FILE* Geöffneter Dateizeiger Muss mit "rb" geöffnet werden Muss mit "wb" geöffnet werden

Die tatsächliche Anzahl der bearbeiteten Bytes istsize × countDer Rückgabewert ist die Anzahl der erfolgreich gelesenen oder geschriebenen Blöcke, nicht die Anzahl der Bytes. Wenn der Rückgabewert kleiner als count ist, kann dies durch Dateiende (EOF) oder einen Lese-/Schreibfehler verursacht werden, die separat geprüft werden müssen.

Binärmodus vs. Textmodus

Bei Verwendung von fread/fwrite muss die Datei im Binärmodus geöffnet werden; andernfalls kann es zu Datenbeschädigung kommen.

Warum der Binärmodus verwendet werden muss

Textmodus unter Windows konvertiert Zeilenumbruchzeichen automatisch\nUmgewandelt in\r\n, was die Integrität binärer Daten zerstört.

Unter Linux/macOS verhalten sich Textmodus und Binärmodus gleich, aber plattformübergreifender Code sollte explizit"b"Flag verwenden, um Portabilität zu gewährleisten.

Beispiel

// Richtige Vorgehensweise: Binärmodus explizit angeben
FILE *fp_read  = fopen("data.bin", "rb");  // Binär lesen
FILE *fp_write = fopen("data.bin", "wb");  // Binär schreiben

// Falscher Ansatz: Textmodus (Daten können unter Windows beschädigt werden)
FILE *fp_bad = fopen("data.bin", "r");     // Fehlendes "b"-Flag

Vergleich mit Textfunktionen

Welche Funktion gewählt wird, hängt vom Datentyp und vom Anforderungsszenario ab:

Funktionen fread / fwrite fprintf / fscanf
Datenverarbeitungsmethode Binärdaten unverändert im Speicher lesen und schreiben Formatbasierte Konvertierung (Zahl ↔ Zeichenkette)
Anwendbare Szenarien Strukturen, Arrays, Bilder, Audio usw. Für Menschen lesbarer Text wie Konfigurationsdateien, Logs, Berichte usw.
Leistung Schnell, kein Konvertierungsaufwand Langsam, die Formatzeichenfolge muss analysiert werden
Precision Kein Präzisionsverlust Gleitkommazahlen können Präzision verlieren
Menschenlesbar Nein (Binärformat) Ja (Klartextformat)

Flussdiagramm zum Lesen/Schreiben von Daten

Die folgende Abbildung zeigt den Datenfluss zwischen fread und fwrite sowie zwischen Datei und Speicher:

fread/fwrite 数据读写流程

Typische Anwendungsszenarien

Lesen und Schreiben von Struktur-Daten

Dies ist das häufigste Anwendungsszenario von fread/fwrite; Sie können eine gesamte Struktur auf einmal als einen einzelnen Datenblock lesen oder schreiben.

Beispiel

#include <stdio.h>
#include <stdlib.h>

// Studenten-Struktur definieren
// Hinweis: Es muss ein Array fester Länge verwendet werden, ein char *name-Zeigermitglied ist nicht erlaubt
typedef struct {
    int id;             // Studenten-ID
    char name[50];      // Name (festes Array, Zeiger nicht erlaubt)
    float score;        // Punktzahl
} Student;

int main() {
    // ===== Struktur in Datei schreiben =====
    Student s = {101, "Alice", 95.5f};  // Daten initialisieren
    FILE *fp = fopen("student.bin", "wb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }
    // Die gesamte Struktur als einen Datenblock schreiben
    size_t written = fwrite(&s, sizeof(Student), 1, fp);
    if (written != 1) {
        printf("Schreiben fehlgeschlagen\n");
    }
    fclose(fp);

    // ===== Struktur aus Datei lesen =====
    fp = fopen("student.bin", "rb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }
    Student loaded;  // Nicht initialisiert, wird von fread gefüllt
    size_t read_count = fread(&loaded, sizeof(Student), 1, fp);
    if (read_count != 1) {
        printf("Lesen fehlgeschlagen oder Dateiende\n");
        fclose(fp);
        return 1;
    }
    printf("ID: %d, Name: %s, Punktzahl: %.1f\n", geladen.id, geladen.name, geladen.score);
    fclose(fp);
    return 0;
}
学号: 101, 姓名: Alice, 成绩: 95.5

Strukturen können keine Zeiger-Member enthalten (wie z. B.char *name), weil fwrite nur den Adresswert des Zeigers speichert, nicht den Inhalt der Zeichenkette, auf die er zeigt. Beim Zurücklesen ist diese Adresse nicht mehr gültig, was zu einem hängenden Zeiger (dangling pointer) führt. Die korrekte Vorgehensweise ist, ein Array fester Länge zu verwenden, wie z. B.char name[50]。

Arraydaten lesen und schreiben

Arrays werden zusammenhängend im Speicher abgelegt, sodass das gesamte Array auf einmal gelesen oder geschrieben werden kann.

Beispiel

#include <stdio.h>

int main() {
    // Rohdaten-Array
    int data[] = {1, 2, 3, 4, 5};
    int count = 5;  // Anzahl der Array-Elemente

    // ===== Array in Datei schreiben =====
    FILE *fp = fopen("array.bin", "wb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }
    // data entspricht &data[0] und zeigt auf die erste Adresse des Arrays
    fwrite(data, sizeof(int), count, fp);
    fclose(fp);
    printf("%d Ganzzahlen in array.bin geschrieben\n", count);

    // ===== Array aus Datei lesen =====
    int buffer[5] = {0};  // Auf 0 initialisieren, um Leseergebnisse leicht überprüfen zu können
    fp = fopen("array.bin", "rb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }
    size_t read_count = fread(buffer, sizeof(int), count, fp);
    if (read_count < count) {
        printf("Unvollständiger Lesevorgang: %d Elemente erwartet, %zu Elemente erhalten"\n", count, read_count);
    }
    fclose(fp);

    // Leseergebnis ausgeben
    printf("Gelesene Daten: ");
    for (int i = 0; i < count; i++) {
        printf("%d ", Puffer[i]);
    }
    printf("\n");
    return 0;
}
已写入 5 个整数到 array.bin
读取到的数据: 1 2 3 4 5

Stapelweises Lesen und Schreiben großer Dateien

Bei großen Dateien sollten Sie eine Schleife zum blockweisen Lesen/Schreiben verwenden, um zu vermeiden, dass der gesamte Inhalt auf einmal in den Speicher geladen wird.

Beispiel

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 1024 // Jedes Mal 1KB lesen/schreiben, nach Bedarf anpassen

// Effiziente Dateikopierfunktion
// Rückgabewert: 0 bedeutet Erfolg, -1 bedeutet Fehler
int copy_file(const char *src, const char *dst) {
    char buffer[BUFFER_SIZE];  // Zwischenpuffer
    FILE *in = fopen(src, "rb");
    FILE *out = fopen(dst, "wb");

    // Prüfen, ob beide Dateien erfolgreich geöffnet wurden
    if (!in) {
        perror("Quelldatei konnte nicht geöffnet werden");
        return -1;
    }
    if (!out) {
        perror("Zieldatei konnte nicht geöffnet werden");
        fclose(in);   // Die geöffnete Quelldatei schließen
        return -1;
    }

    // In einer Schleife lesen, jeweils bis zu BUFFER_SIZE Bytes
    size_t n;
    while ((n = fread(buffer, 1, BUFFER_SIZE, in)) > 0) {
        // fwrite mit size=1, count=n stellt sicher, dass die tatsächliche Anzahl der gelesenen Bytes geschrieben wird
        if (fwrite(buffer, 1, n, out) != n) {
            perror("Schreiben fehlgeschlagen");
            fclose(in);
            fclose(out);
            return -1;
        }
    }

    // Prüfen, ob das Lesen aufgrund eines Fehlers beendet wurde (statt eines normalen EOF)
    if (ferror(in)) {
        perror("Lesefehler");
        fclose(in);
        fclose(out);
        return -1;
    }

    fclose(in);
    fclose(out);
    printf("Datei erfolgreich kopiert: %s -> %s\n"\n", src, dst);
    return 0;
}

int main() {
    // Verwendungsbeispiel: die Beispiel-Testdatei kopieren
    copy_file("example_input.bin", "example_output.bin");
    return 0;
}
文件复制成功: example_input.bin -> example_output.bin

Der Schlüssel zu diesem Modus ist, dassfreaddie Anzahl der tatsächlich gelesenen Bytes zurückgibt; verwenden Sie dannfwrite, um nur die Anzahl der tatsächlich gelesenen Bytes zu schreiben und das Schreiben zusätzlicher Daten am Ende zu vermeiden.

Wichtige Überlegungen

Prüfung des Rückgabewerts

Nach dem Aufruf von fread/fwrite müssen Sie den Rückgabewert prüfen, um zwischen normalem Abschluss und abnormalen Bedingungen zu unterscheiden.

Beispiel

#include <stdio.h>

int main() {
    int buffer[100];
    FILE *fp = fopen("data.bin", "rb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }

    // Versuche, 100 ganze Zahlen zu lesen
    size_t count = fread(buffer, sizeof(int), 100, fp);

    if (count < 100) {
        // EOF von Fehlern unterscheiden
        if (feof(fp)) {
            printf("Ende der Datei erreicht, tatsächlich %zu Elemente gelesen\n"\n", count);
            // An diesem Punkt können wir mit der Verarbeitung der gelesenen Daten fortfahren
        } else if (ferror(fp)) {
            perror("Beim Lesen ist ein Fehler aufgetreten");
            fclose(fp);
            return 1;
        }
    } else {
        printf("Alle %zu Elemente erfolgreich gelesen\n", count);
    }

    fclose(fp);
    return 0;
}
已到达文件末尾,实际读取 47 个元素

Fallstricke beim Lesen/Schreiben von Structs

Das Speicherlayout von Strukturen wird durch die Ausrichtungsstrategie des Compilers beeinflusst, was zu plattformübergreifenden Kompatibilitätsproblemen führen kann.

Inkonsistente Speicherausrichtung

Bei unterschiedlichen Compilern oder unterschiedlichen Kompilierungsoptionen kann dieselbe Struktur unterschiedliche Speicherlayouts aufweisen.

Beispiel

#include <stdio.h>

// Beispielproblem: Bei Standardausrichtung gibt es 3 Byte Padding nach char
typedef struct {
    char c;      // Belegt 1 Byte, aber danach folgt Padding
    int i;       // 4-Byte-Ausrichtung, beginnt tatsächlich bei Offset 4
} Mixed;

// Lösung 1: pragma pack verwenden, um kompakte Ausrichtung zu erzwingen
#pragma pack(push, 1) // Aktuelle Ausrichtungseinstellungen speichern, auf 1-Byte-Ausrichtung setzen
typedef struct {
    char c;      // Offset 0, belegt 1 Byte
    int i;       // Offset 1, belegt 4 Byte (kein Padding)
} PackedMixed;
#pragma pack(pop) // Vorherige Ausrichtungseinstellungen wiederherstellen

// Lösung 2: Member manuell nacheinander serialisieren (der sicherste Weg)
// Diese Methode hängt nicht von der Compiler-Ausrichtung ab und bietet die beste plattformübergreifende Kompatibilität

int main() {
    // Größenunterschied zwischen den beiden Strukturen ausgeben
    printf("Größe von standardmäßig ausgerichtetem Mixed: %zu bytes\n", sizeof(Mixed));       // Normalerweise 8
    printf("Größe von kompakt ausgerichtetem PackedMixed: %zu bytes\n", sizeof(PackedMixed)); // Sie ist 5
    return 0;
}
默认对齐 Mixed 大小: 8 字节
紧凑对齐 PackedMixed 大小: 5 字节

Empfohlene VorgehensweiseFür plattformübergreifende oder langfristige Speicherung ist die manuelle Serialisierung (Lesen/Schreiben Member für Member) dem direkten Schreiben der Struktur in eine Datei vorzuziehen. Dadurch lassen sich Ausrichtungs- und Byte-Reihenfolge-Probleme vollständig vermeiden.

Zeiger-Member sind verboten

Dies ist einer der häufigsten Fehler von Anfängern.

Beispiel

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Falsches Beispiel: struct enthält ein Zeiger-Member
typedef struct {
    int id;
    char *name;   // Gefahr! fwrite speichert nur den Zeigerwert (Adresse), nicht den String-Inhalt
} BadStudent;

// Korrektes Beispiel: ein festes Array verwenden
typedef struct {
    int id;
    char name[100];  // Die tatsächlichen Daten werden in der Struktur gespeichert, sodass sie sicher gelesen und geschrieben werden können
} GoodStudent;

int main() {
    // Die Folgen des falschen Ansatzes demonstrieren
    BadStudent bs;
    bs.id = 1;
    bs.name = malloc(50);
    strcpy(bs.name, "EXAMPLE");

    // BadStudent schreiben: An diesem Punkt wird die Adresse des name-Zeigers geschrieben, aber der String-Inhalt wird nicht geschrieben
    FILE *fp = fopen("bad.bin", "wb");
    fwrite(&bs, sizeof(BadStudent), 1, fp);
    fclose(fp);

    // BadStudent zurücklesen: Der Wert des name-Zeigers ist die Adresse aus einem vorherigen malloc in einem anderen Prozess, vollständig ungültig
    BadStudent geladen;
    fp = fopen("bad.bin", "rb");
    fread(&loaded, sizeof(BadStudent), 1, fp);
    fclose(fp);
    // loaded.name ist ein hängender Zeiger; der Zugriff darauf führt zu undefiniertem Verhalten!

    free(bs.name);
    printf("Demo abgeschlossen, aber der geladene name-Zeiger ist ungültig\n");
    return 0;
}
演示完成,但 load 出来的 name 指针是无效的

Puffer leeren

Die E/A der C-Standardbibliothek ist gepuffert; kritische Daten müssen explizit geleert werden, um die Persistenz zu gewährleisten.

Beispiel

#include <stdio.h>

int main() {
    FILE *fp = fopen("critical.bin", "wb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }

    int data[] = {1, 2, 3, 4, 5};
    fwrite(data, sizeof(int), 5, fp);

    // Methode 1: Puffer explizit leeren, Daten werden sofort auf die Festplatte geschrieben
    fflush(fp);
    printf("Verwende fflush, um das Leeren auf die Festplatte zu erzwingen\n");

    // Methode 2: Automatisch beim Schließen der Datei leeren (empfohlen)
    // fclose ruft intern automatisch fflush auf
    fclose(fp);
    printf("fclose leert den Puffer ebenfalls automatisch\n");

    return 0;
}
使用 fflush 强制刷新到磁盘
fclose 也会自动刷新缓冲区

Positionierung des Dateizeigers

Die Kombination von fseek und ftell ermöglicht den wahlfreien Zugriff auf eine Datei und das Lesen von Daten an angegebenen Positionen nach Bedarf.

Beispiel

#include <stdio.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    FILE *fp = fopen("students.bin", "rb");
    if (fp == NULL) {
        perror("Datei konnte nicht geöffnet werden");
        return 1;
    }

    // Zum Ende der Datei springen, um die Dateigröße zu ermitteln
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    printf("Dateigröße: %ld Bytes\n", file_size);

    // Berechne, wie viele Struct-Datensätze in der Datei sind
    long record_count = file_size / sizeof(Student);
    printf("Enthält %ld Studentendatensätze\n", record_count);

    // Zum 6. Struct springen (Index beginnt bei 0, Offset = 5 * sizeof(Student))
    fseek(fp, sizeof(Student) * 5, SEEK_SET);
    Student s;
    fread(&s, sizeof(Student), 1, fp);
    printf("Datensatz #6: id=%d, name=%s\n", s.id, s.name);

    fclose(fp);
    return 0;
}
文件大小: 6000 字节
包含 100 条学生记录
第 6 条记录: id=106, name=EXAMPLE

Zusammenfassung häufiger Fehler

Die folgende Tabelle fasst die häufigsten Probleme und korrekten Vorgehensweisen bei der Verwendung von fread/fwrite zusammen:

Fehlertyp Fehlersymptom Korrekte Vorgehensweise
Ungeprüfter Dateizeiger fread direkt nach fopen aufzurufen, wenn fopen fehlschlägt, verursacht einen Absturz. Immer prüfenfp != NULL
Binärmodus ignorieren Binärdaten werden unter Windows durch Zeilenumbruchkonvertierung beschädigt. Verwendung"rb" / "wb"
Verwechslung der Blockanzahl mit der Byteanzahl Behandlung des Rückgabewerts als Anzahl von Bytes, was zu Logikfehlern führt. Beachten Sie, dass der Rückgabewert die Anzahl der Blöcke ist; Anzahl der Bytes =Rückgabewert × Größe
Pufferüberlauf Nicht ausreichend zugewiesener Speicher, Schreibzugriff außerhalb der Grenzen Pufferplatz ≥size × count sicherstellen
Struct enthält Zeiger Zeigeradresse ist nach dem Zurücklesen ungültig, was zu einem Dangling Pointer führt. Verwenden Sie ein festes Array oder serialisieren Sie jedes Member manuell.
Ignorieren von Fehlerprüfungen EOF kann nicht von Lesefehlern unterschieden werden, was zu unvollständigen Daten führt. Rückgabewert prüfen,feof() / ferror() verwenden,Ursache bestimmen
Daten nicht synchronisiert Programmabsturz führt zu Datenverlust im Puffer Nach dem Schreiben kritischer Datenfflush(fp) aufrufen
Weitere Erweiterungen