14.8    Wahlfreier Dateizugriff

Wenn Sie eine Datei öffnen, verweist ein Indikator für die Dateiposition (der Schreib-/Lesezeiger) auf den Anfang der Datei, genauer gesagt: auf das erste Zeichen mit der Position 0. Öffnen Sie eine Datei im Anhängemodus (a bzw. a+), verweist der Schreib-/Lesezeiger auf das Ende der Datei. Mit jeder Lese- oder Schreiboperation erhöht sich auch der Schreib-/Lesezeiger um die Anzahl der übertragenen Zeichen. Möchten Sie diesen sequenziellen Arbeitsfluss von Dateien ändern, müssen Sie Funktionen für einen wahlfreien Dateizugriff verwenden. Hierzu stehen die Funktionen fseek(), rewind() und fsetpos() zur Verfügung.

14.8.1    Die aktuelle Dateiposition ermitteln

Müssen Sie eine aktuelle Dateiposition im Programm abfragen, und wollen Sie beispielsweise erst später wieder auf diese Position zugreifen, bietet C in der Header-Datei stdio.h zwei verschiedene Funktionen an. Einmal gibt es die Funktion ftell():

long int ftell( FILE* fp );

Diese Funktion liefert die aktuelle Schreib-/Lese-Position des Streams fp zurück. Erhalten Sie einen Fehler zurück, dann lautet der Wert –1L. Ähnlich funktioniert auch die Funktion fgetpos():

int fgetpos( FILE* restrict fp, fpos_t* restrict pos );

Bei dieser Funktion wird die aktuelle Schreib-/Lese-Position des Streams fp an das mit pos referenzierte Speicherobjekt vom Typ fpos_t (meistens ein typedef auf long oder long long) geschrieben. Die Funktion gibt 0 zurück, wenn alles in Ordnung war, ansonsten wird ein Wert ungleich 0 zurückgegeben.

14.8.2    Die aktuelle Dateiposition ändern

Die Positionen von Schreib- und Lesezeigern ändern Sie durch drei in der Header-Datei definierte Funktionen. Zunächst die Syntax der Funktion fseek():

#include <stdio.h>
int fseek( FILE* fp, long offset, int origin );

Damit wird der Schreib-/Lesezeiger vom Stream fp durch die Angaben von offset relativ vom Bezugspunkt origin versetzt. Für origin sind folgende drei Konstanten definiert:

Makro

Bezugspunkt ab …

SEEK_SET

… dem Anfang der Datei

SEEK_CUR

… der aktuellen Position

SEEK_END

… dem Ende der Datei

Tabelle 14.7    Konstanten für den Bezugspunkt origin in der Funktion fseek()

Trat bei der Funktion fseek() kein Fehler auf, wird 0 zurückgegeben und gegebenenfalls auch das EOF-Flag gelöscht. Im Fehlerfall ist der Rückgabewert der Funktion ungleich 0.

Die zweite Funktion zum Ändern des Schreib-/Lesezeigers und so etwas wie das Gegenstück zur Funktion fgetpos() ist die Funktion fsetpos(). Hier die Syntax dazu:

#include <stdio.h>
int fsetpos( FILE* fp, const fpos_t* pos );

Hiermit setzen Sie den Schreib-/Lesezeiger des Streams fp auf den Wert, der durch pos referenziert wird. In der Regel sollte diese Position ein Wert von der zuvor aufgerufenen Funktion fgetpos() sein. Wurde die Funktion erfolgreich ausgeführt, wird 0 zurückgegeben und ebenfalls das Flag des End-of-File und Error indicator gelöscht. Bei einem Fehler wird ein Wert ungleich 0 zurückgegeben.

Mit der Funktion rewind() gibt es noch eine dritte Funktion zum Verändern der Dateiposition. Wie Sie am Namen schon ablesen können, setzt diese Funktion den Schreib-/Lesezeiger auf den Anfang der Datei. Hier die Syntax dazu:

#include <stdio.h>
void rewind( FILE* fp );

Nachdem der Schreib-/Lesezeiger des Streams fp auf den Anfang gesetzt wurde, wird auch das Flag des End-of-File und des Error Indicators gelöscht. Ein Aufruf von rewind(fp) entspricht folgendem Aufruf:

(void)fseek( fp, 0L, SEEK_SET );

Hierzu folgt ein einfaches Beispiel, das Ihnen den sogenannten wahlfreien Dateizugriff (random access) in der Praxis demonstriert. Wahlfreier Zugriff heißt, dass man an jeder beliebigen Stelle der Datei auf die Daten zugreifen kann.

00  // Kapitel14/random_access.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define FILENAME "listing009.c" // Anpassen

04 void dump_buffer(FILE *fp) {
05 int ch;
06 while( (ch = fgetc(fp)) != EOF && ch != '\n' )
07 /* Kein Anweisungsblock nötig */ ;
08 }

09 long fileSize( FILE *fp ) {
10 if( fseek( fp, 0L, SEEK_END ) != 0 ) {
11 fprintf(stderr, "Fehler bei fseek\n");
12 return -1;
13 }
14 return ftell(fp);
15 }

16 int main (void) {
17 long endPos = 0, aktPos = 0, neuPos = 0;
18 FILE *rfp = fopen(FILENAME, "r");
19 if( rfp != NULL ) {
20 endPos = fileSize( rfp );
21 if(endPos == -1 ) {
22 return EXIT_FAILURE;
23 }
24 }
25 else {
26 fprintf(stderr, "Fehler beim Oeffnen\n");
27 return EXIT_FAILURE;
28 }
29 rewind( rfp ); // Wieder zum Anfang zurück
30 printf("Lesezeiger nach vorne setzen. Um wie viel: ");
31 if (scanf("%ld", &neuPos) != 1 ) {
32 fprintf(stderr, "Fehler bei der Eingabe\n");
33 return EXIT_SUCCESS;
34 }
35 dump_buffer(stdin);
36 if( neuPos > endPos ) {
37 fprintf(stderr,
"Fehler: Datei ist nur %ld Byte groß\n",endPos);
38 }
39 if( fseek( rfp, neuPos, SEEK_CUR ) != 0 ) {
40 fprintf(stderr, "Fehler bei fseek\n");
41 return EXIT_FAILURE;
42 }
43 int c;
44 while( (c=fgetc(rfp)) != EOF ) {
45 fputc(c, stdout);
46 }
47 fclose(rfp);
48 return EXIT_SUCCESS;
49 }

Listing 14.7    random_access.c demonstriert den wahlfreien Zugriff auf Dateien.

Nachdem in Zeile (18) eine Datei geöffnet worden ist, stellen Sie die Dateigröße in Zeile (20) mit der selbst geschriebenen Funktion fileSize() fest. Diese Funktion wurde in den Zeilen (09) bis (15) definiert. In der Funktion fileSize() wird zunächst in Zeile (10) der Stream mit fseek() auf das Dateiende gesetzt. Diese Position wird in Zeile (14) mit der Funktion ftell() an den Aufrufer zurückgegeben. In der Variablen endPos in Zeile (20) steht dann quasi die Dateigröße in Bytes. In Zeile (29) setzen Sie den Lesezeiger des Streams mit rewind() wieder auf den Anfang der Datei zurück.

In den Zeilen (30) und (31) werden Sie gefragt, um wie viele Bytes Sie dem Anfang des Lesezeigers nach vorne versetzen wollen. Den eingegebenen Wert überprüfen Sie zunächst in Zeile (36) mit der zuvor ermittelten Dateigröße. Damit verschieben Sie den Lesezeiger nicht über das Dateiende hinaus, denn das würde zu einem Fehler führen. In Zeile (39) wird der Lesezeiger des Streams dann von der aktuellen Position aus, dem Dateianfang, auf die neue Position gesetzt, und in den Zeilen (44) bis (46) werden dann die Daten ab der neuen Position Zeichen für Zeichen eingelesen und ausgegeben.