16.26 Low-Level-Datei-I/O-Funktionen (nicht ANSI C) 

Mit den Funktionen der höheren Ebene (High-Level) wurde auf die Daten mit einem hohen Abstraktionsniveau zugegriffen. Das bedeutet, die Daten eines Programms wurden formatiert ausgegeben oder eingelesen. Bei der niedrigeren Ebene (Low-Level) wird auf einem tieferen Niveau gearbeitet. Der Zugriff auf die Daten findet als eine Folge von unstrukturierten Bytes statt und bietet somit die Möglichkeit, Bytesequenzen vorgegebener Länge einzulesen oder auszugeben. Nicht nur die Namen der Funktionen der höheren und der niedrigeren Ebene sind ähnlich, auch ihre Abarbeitung ist es – nur mit einem, aber sehr prinzipiellen Unterschied, der etwas verwirrend ist. Während beim High-Level-Dateizugriff mit einem FILE-Zeiger (Stream) auf die Datei zugegriffen wird, geschieht dies auf der niedrigeren Ebene mit einem sogenannten File-Deskriptor. Dieser Deskriptor ist kein Zeiger wie bei der höheren Ebene, sondern ein normaler int-Wert, der beim Öffnen einer Datei zurückgegeben wird.
Die höheren Dateifunktionen bauen auf den Funktionen der niedrigeren Ebene auf. Die niedrigere Ebene ist also die Grundlage der höheren Funktionen in der Standard-Bibliothek. Doch allein die Funktionen der niedrigeren Ebene arbeiten ungepuffert.
Wenn zum Beispiel Bibliotheksfunktionen wie printf() oder scanf() zum Aus- bzw. Eingeben benutzt werden, verwenden Sie eigentlich die Systemfunktionen write() und read(). Dies sind Funktionen (Systemfunktionen) im Low-Level-Bereich. Also ist es egal, ob fgets(), fputs(), gets(), puts(), putc(), getc() usw. eingesetzt werden, all diese Funktionen bauen auf Systemfunktionen auf.
Natürlich ist anzumerken, dass diese Funktionen der niedrigeren Ebene systemabhängig sind und somit nicht dem ANSI-C-Standard entsprechen können. Das heißt konkret, dass es Probleme mit der Portabilität der Programme geben kann.
Hinweis |
Bei Linux/UNIX-Betriebssystemen sind Low-Level-Funktionen direkte Schnittstellen zum System. Dateizugriffe mit diesen Funktionen realisieren direkt entsprechende Betriebssystemfunktionen (sogenannte System Calls). |
16.26.1 Datei öffnen – »open()« 

Öffnen Sie eine Datei mit der Funktion open(), so wird der Datei ein Deskriptor – auch File-Deskriptor genannt – zugeordnet. Ein File-Deskriptor ist eine positive kleine Zahl, die vom Betriebssystem vergeben wird. Über diesen Deskriptor geben Sie an, ob gelesen oder geschrieben werden soll. Mit der Funktion open() kann zudem auch gleich eine Datei angelegt werden. Hier sehen Sie die Syntax mit den dazugehörenden Headerdateien, die mit eingebunden werden müssen, für Linux/UNIX:
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int open(const char *pfad, int modus); int open(const char *pfadname, int flags, mode_t zugriffsrechte);
Die Syntax mit Headerdateien für Windows/MS-DOS sieht so aus:
#include <fcntl.h> #include <io.h> #include <sys\stat.h> int open(const char *pfad, int modus); int open(const char *pfadname, int flags, mode_t zugriffsrechte);
Als Pfadangabe müssen Sie den absoluten oder relativen Pfad der Datei angeben, die geöffnet werden soll. Da der Prototyp (unter Linux/UNIX zumindest) in Wirklichkeit korrekt open(const char *, int, ...) lautet, kann je nach Situation das dritte Argument auch wegfallen.
Beim Modus gibt es mehrere Varianten, aber diese sind systemabhängig. Einer der drei Modi, die in Tabelle 16.11 aufgeführt sind, muss immer angegeben werden.
Modus | Bedeutung |
O_WRONLY |
nur zum Schreiben öffnen |
O_RDWR |
zum Lesen und Schreiben öffnen |
O_RDONLY |
nur zum Lesen öffnen |
Dies sieht dann unter Linux/UNIX folgendermaßen aus:
int fh; if((fh = open("/home/programmname", O_RDONLY)) != -1)
oder unter Windows/MS-DOS:
if((fh = open("c:\\config.sys", O_RDONLY)) != -1)
In den beiden Beispielen wird eine Datei nur zum Lesen geöffnet. Bei einem dieser drei genannten Modi können Sie mit dem bitweisen ODER-Zeichen (|) weitere Aktionen verknüpfen. Dies sieht dann so aus:
if((fh = open("c:\\test.sys", O_WRONLY|O_CREAT)) != -1)
Hiermit wird eine Datei zum Schreiben geöffnet. Existiert diese Datei nicht, so wird sie erzeugt (O_CREAT). Existiert diese Datei, so wird der zusätzliche Modus O_CREATE ignoriert. Tabelle 16.12 zeigt die zusätzlichen Modi, die für Linux/UNIX und Windows/MS-DOS darüber hinaus zur Verfügung stehen.
Modus | Bedeutung |
O_CREAT |
Falls die Datei nicht existiert, wird sie neu angelegt. Falls die Datei existiert, ist O_CREAT ohne Wirkung. |
O_APPEND |
Datei öffnen zum Schreiben am Ende |
O_EXCL |
O_EXCL kombiniert mit O_CREAT bedeutet, dass die Datei nicht geöffnet werden kann, wenn sie bereits existiert und open() den Wert –1 zurückliefert (–1 == Fehler). |
O_TRUNC |
Eine Datei, die zum Schreiben geöffnet wird, wird geleert. Darauffolgendes Schreiben bewirkt erneutes Beschreiben der Datei von Anfang an. Die Attribute der Datei bleiben erhalten. |
Beispielsweise wird mit
if((fh = open("/home/Name.txt", O_WRONLY | O_TRUNC)) != -1)
erreicht, dass die Datei Name.txt zum Schreiben geöffnet wird. Der Inhalt dieser Datei wird gelöscht, und sie kann neu beschrieben werden. Tabelle 16.13 listet weitere Modi auf, die nur für Linux/UNIX zur Verfügung stehen.
Modus | Bedeutung |
O_NOCTTY |
Falls der Pfadname der Name eines Terminals ist, so sollte dieses nicht das neue Kontrollterminal des Prozesses werden, sofern der aktuelle Prozess kein Kontrollterminal besitzt. |
O_NONBLOCK |
Falls der Pfadname der Name eines FIFOs oder einer Gerätedatei ist, wird der Prozess beim Öffnen und bei nachfolgenden I/O-Operationen nicht blockiert. Dieses Flag zeigt seine Wirkung erst bei einer Pipe oder nichtblockierenden Sockets. |
O_SYNC |
Jeder Schreibvorgang auf das Medium wird direkt ausgeführt, und es wird gewartet, bis der Schreibvorgang komplett beendet wurde. Dieses Flag setzt den Pufferungsmechanismus außer Kraft. O_SYNC wird nicht von POSIX.1 unterstützt, wohl aber von SVR4. |
Für Windows/MS-DOS gibt es die Extra-Modi, die in Tabelle 16.14 aufgelistet sind.
Modus | Bedeutung |
O_BINARY |
Legt den Binärmodus der Datei fest. |
O_TEXT |
Legt den Textmodus der Datei fest. |
Jetzt folgen noch einige Modi, mit denen die Zugriffsrechte auf eine Datei erteilt werden können. Die Modi für Windows/MS-DOS sehen Sie in Tabelle 16.15.
Modus für Zugriffe | Bedeutung |
S_IWRITE |
Schreiben erlaubt |
S_IREAD |
Lesen erlaubt |
S_IREAD | SIWRITE |
Lesen und Schreiben erlaubt |
Mit folgender Zeile wird z. B. eine Datei erzeugt, die nur gelesen werden darf:
if((fh=open("new.xxx", O_CREAT , S_IREAD)) == -1)
Wenn das Lesen und Schreiben erlaubt sein sollen, sieht dies so aus:
if((fh=open("new.xxx",O_CREAT, S_IREAD|S_IWRITE)) == -1)
Diese drei Zugriffsrechte für Windows/MS-DOS stehen in der Headerdatei <sys\stat.h>.
Für Linux/UNIX können Sie folgende Zugriffsrechte erteilen, die sich in der Headerdatei <sys/stat.h> befinden. Tabelle 16.16 enthält die Modi für Zugriffsrechte unter Linux/UNIX.
Modus für Zugriffe | Bedeutung |
S_ISUID |
Set-user-ID-Bit |
S_ISGID |
Set-group-ID-Bit |
S_ISVTX |
Sticky Bit (saved-text Bit) |
S_IRUSR |
read (user; Leserecht für Eigentümer) |
S_IWUSR |
write (user; Schreibrecht für Eigentümer) |
S_IXUSR |
execute (user; Ausführungsrecht für Eigentümer) |
S_IRWXU |
read, write, execute (user; Lese-, Schreib-, Ausführungsrecht für Eigentümer) |
S_IRGRP |
read (group; Leserecht für Gruppe) |
S_IWGRP |
write (group; Schreibrecht für Gruppe) |
S_IXGRP |
execute (group; Ausführungsrecht für Gruppe) |
S_IRWXG |
read, write, execute (group; Lese-, Schreib-, Ausführungsrecht für Eigentümer) |
S_IROTH |
read (other; Leserecht für alle anderen Benutzer) |
S_IWOTH |
write (other; Schreibrecht für alle anderen Benutzer) |
S_IXOTH |
execute (other; Ausführungsrecht für alle anderen Benutzer) |
S_IRWXO |
read, write, execute (other; Lese-, Schreib-, Ausführungsrecht für alle anderen Benutzer) |
Hinweis |
Sofern Sie mit der oktalen Schreibweise der Rechtevergabe vertraut sind, können Sie natürlich diese als Alternative verwenden, beispielsweise so: fd = open( new_file, O_WRONLY | O_EXCL | O_CREAT, 0644); |
Außerdem sollten Sie noch wissen, dass bei Verwendung des Flags O_CREAT die Zugriffsrechte unter Linux/UNIX nicht unbedingt gewährt werden müssen, da die Einschränkungsmaske die Vergabe von Rechten verhindern kann (wird) – und somit die Rechte selbst. Aus diesem Grund wurde mithilfe der Funktion umask() die Maske zur Wegnahme von Rechte-Bits auf 0 gesetzt, womit alle Zugriffsrechte in dieser Maske erlaubt werden.
Tipp |
Standardmäßig wird meistens die Einschränkungsmaske 022 vergeben. Es ist aber auch möglich, mit dem Shell-Built-in-Kommando umask die eigene Einschränkungsmaske zu ändern. Innerhalb eines Listings z. B. würde die neu gesetzte umask von 0 nur während der Ausführung des Programms (und der Unterprozesse) gültig. Dazu kann man beispielsweise einen entsprechenden umask-Aufruf in einer Startup-Datei wie .profile eintragen, sodass beim Start einer entsprechenden Shell die Einschränkungsmaske automatisch gesetzt wird. |
Im folgenden Listing soll eine Datei zum Lesen und Schreiben geöffnet werden. Existiert diese nicht, wird eine neue Datei erzeugt. Falls sie existiert, wird der Inhalt gelöscht und neu beschrieben. Hier sehen Sie den Quellcode, der portabel gehalten wurde:
/* open1.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #endif int main(void) { int fh; if((fh=open("adress.txt", O_RDWR|O_CREAT|O_TRUNC))==-1) { perror("Fehler bei open()"); return EXIT_FAILURE; } close(fh); return EXIT_SUCCESS; }
Falls Sie jetzt noch die Zugriffsrechte auf diese Datei vergeben wollen, muss für Linux/UNIX die Headerdatei <sys/stat.h> und für MS-DOS/Windows <sys\stat.h> eingebunden werden (beachten Sie den Slash und Backslash). Soll beispielsweise der User unter Linux diese Datei nur lesen dürfen, so muss nur mithilfe des ODER-Operators der Modus S_IRUSR hinzugefügt werden:
// alle Zugriffsrechte der Einschränkungsmaske erlauben umask(0); open("adress.txt", O_RDWR|O_CREAT|O_TRUNC, S_IRUSR)) == -1)
Ein weiteres Beispiel mit open() ist z. B. das Aufrufen von Programmen aus der Kommandozeile:
/* open2.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif int main(int argc, char *argv[]) { int fh; if( (fh=open(*++argv,O_RDONLY)) == -1) perror(*argv); else close(fh); return EXIT_SUCCESS; }
In diesem Beispiel wird eine Datei zum Lesen geöffnet, deren Name als zweites Argument in der Kommandozeile angegeben wurde. Falls die Datei nicht existiert, wird eine entsprechende Fehlermeldung wie
Programmname : No such File in Directory
ausgegeben. Danach wird der File-Deskriptor wieder geschlossen. Falls die Datei, die eben aufgerufen wurde, nicht existiert, aber anschließend erzeugt werden soll, dann ist dies so möglich:
/* open3.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif int main(int argc,char *argv[]) { int fh; if(*++argv == NULL) return EXIT_FAILURE; /* keine Argumente vorhanden */ if( (fh = open(*argv, O_RDONLY)) == -1) if( (fh = open(*argv,O_RDWR|O_CREAT)) == -1) perror(*argv); close(fh); return EXIT_SUCCESS; }
Hiermit wird zuerst versucht, die Datei zu öffnen. Falls die Datei nicht existiert, wird sie gleich zum Lesen und Schreiben erzeugt.
Bei der Erläuterung der Low-Level-Datei-E/A-Funktionen konnten Sie schon erkennen, warum eher auf die höhere Ebene zurückgegriffen wird. Wer auf mehreren Systemen programmiert, kann schnell durcheinanderkommen. Was bei dem einen System gelingt, ist bei dem anderen nicht machbar. Andererseits ist es durchaus hilfreich, beim Erzeugen einer Datei die Zugriffsrechte von Beginn an festzulegen (das gilt speziell unter Linux/UNIX). Daraus ergibt sich, dass Low-Level-Datei-E/A-Funktionen vorwiegend zur Systemprogrammierung eingesetzt werden.
Vor allem für Linux/UNIX-Programmierer ist diese Art, eine Datei (auch Gerätedateien) zu öffnen, eine sehr wichtige Schnittstelle. Beispielsweise kann man hier mit folgendem einfachen Code-Konstrukt etwas auf dem Drucker ausgeben lassen:
int fd; // Drucker auf /dev/lp0 zum Schreiben öffnen fd = open("/dev/lp0", O_WRONLY); if(fd >= 0) // drucken ... write(fd, buf, buf_size); close(fd);
16.26.2 Datei schließen – »close()« 

close() dient dazu, eine Datei mit dem Deskriptor fh zu schließen, die zuvor mit open() oder create() geöffnet bzw. erzeugt wurde. Denn auch mit der Funktion open() kann nur eine bestimmte Anzahl von Dateien gleichzeitig geöffnet werden. Die Anzahl der maximal offenen Dateien ist in der Konstante OPEN_MAX deklariert. Hier sehen Sie die Syntax zu close():
int close(int fh);
Der Rückgabewert der Funktion ist bei Fehler –1, ansonsten 0. Hierzu noch ein kurzes Listing:
/* close.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #endif int main(void) { int fh; if((fh=open("adressen.txt", O_RDONLY | O_CREAT)) == -1) { perror("Fehler bei open"); return EXIT_FAILURE; } if((close(fh)) == -1) printf("Fehler beim Schliessen der Datei\n"); else printf("Datei wurde ordentlich geschlossen\n"); return EXIT_SUCCESS; }
Sie öffnen hier mit open() eine Datei zum Lesen. Falls diese nicht existiert, wird eine neue erzeugt. Danach wird der Deskriptor wieder mit einer Überprüfung geschlossen, ob der Schließvorgang ordnungsgemäß verlief. Beim Programmende schließen sich die offenen Deskriptoren selbst.
16.26.3 Datei erzeugen – »creat()« 

Außer mit open() kann auch mit der Funktion creat() eine neue Datei angelegt werden. Die Syntax von creat() sieht bei Linux/UNIX so aus:
#include <fcntl.h> #inlcude <sys/types.h> #include <sys/stat.h> int creat(const char *pfad, int modus);
und unter Windows/MS-DOS so:
#include <fcntl.h> #inlcude <io.h> #include <sys\stat.h> int creat(const char *pfad, int modus);
creat() arbeitet genauso wie die Funktion open(). pfad ist der Name der neu anzulegenden Datei mit dem Pfad. Mit modus sind die Modi gemeint, die im Abschnitt zu open() geschildert wurden. Existiert eine Datei bereits, wird diese geöffnet und geleert.
Mit der Funktion open() und den Modi O_CREAT und O_TRUNC erreichen Sie dasselbe wie mit creat(). Und somit ist die Funktion creat() eigentlich völlig überflüssig und umständlich, da eine neu mit creat() angelegte Datei nur beschrieben werden kann. Um diese Datei lesen zu können, muss sie zuerst mit close() geschlossen werden, um sie anschließend mit open() zum Lesen zu öffnen.
creat() wurde zu einer Zeit benötigt, als die Funktion open() noch nicht die Angabe von O_CREAT kannte. Jetzt bleibt die Funktion natürlich weiterhin bestehen, da sonst alte Programme, die zu dieser Zeit entwickelt wurden, ohne Änderung am Quellcode nicht mehr übersetzt werden können.
16.26.4 Schreiben und Lesen – »write()« und »read()« 

Wir betrachten zuerst die Syntax der Funktion write ():
#include <unistd.h> /* für UNIX/LINUX */ #include <io.h> /* für MS-DOS */ int write(int fh, const void *puffer, size_t bytezahl);
Mit der Funktion write() wird unformatiert in die Datei mit dem File-Deskriptor fh geschrieben. Um den geeigneten File-Deskriptor zu erhalten, muss die Datei zuvor mit open() oder create() geöffnet werden. Dann schreibt write() von der Datei mit dem fh-File-Deskriptor bytezahl Bytes in die Speicheradresse von puffer. Dieser ist wieder ein typenloser void-Zeiger und kann somit jeden beliebigen Datentyp annehmen. Bei einem Fehler liefert diese Funktion den Wert –1 zurück, ansonsten die Anzahl der erfolgreich geschriebenen Bytes. Hierzu ein einfaches Beispiel mit write():
/* write1.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #endif int main(void) { int fh; char puffer[100]; strcpy(puffer,"Dieser Text steht in \"test.txt\"\n"); if((fh=open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1) { perror(NULL); return EXIT_FAILURE; } if((write(fh, &puffer, sizeof(puffer))) == -1) { perror("Fehler bei write"); return EXIT_FAILURE; } printf("Erfolgreich in \"test.txt\" geschrieben\n"); return EXIT_SUCCESS; }
Zuerst wird mit strcpy() ein String in das Array puffer kopiert. Danach wird mit open() die Datei test.txt geöffnet bzw. erzeugt. In den von open() zurückgegebenen File-Deskriptor werden dann mit der Funktion write() von der Adresse puffer Bytes der Anzahl sizeof(puffer) geschrieben.
Die Funktion write() eignet sich genauso wie fwrite() dazu, ganze Strukturen auf einmal zu schreiben, wie das folgende Listing demonstriert:
/* write2.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #ifdef __linux__ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif #define MAXADRESSEN 10 #define MAX 30 struct kunde { char name[MAX]; char vorname[MAX]; int kundenummer; char ort[MAX]; char strasse[MAX]; int hausnummer; int vorwahl; int telefonnr; }; struct kunde k[MAXADRESSEN]; static int counter=0; void neukunde(void) { int fh; if(counter==MAXADRESSEN) printf("Kein Speicherplatz mehr frei!!!\n"); else { printf("Name...................: "); fgets(k[counter].name, MAX, stdin); printf("Vorname................: "); fgets(k[counter].vorname, MAX, stdin); k[counter].kundenummer=counter; printf("Ort....................: "); fgets(k[counter].ort, MAX, stdin); printf("Strasse................: "); fgets(k[counter].strasse, MAX, stdin); printf("Hausnummer.............: "); do { scanf("%d",&k[counter].hausnummer); } while(getchar() != '\n'); printf("Vorwahl................: "); do { scanf("%d",&k[counter].vorwahl); } while(getchar() != '\n'); printf("Telefonnummer..........: "); do { scanf("%d",&k[counter].telefonnr); } while(getchar() != '\n'); if((fh=creat("kunden.dat",S_IREAD|S_IWRITE)) == -1) printf("Konnte\"kunden.dat\" nicht öffnen\n"); else if((write(fh,&k,sizeof(k))) == -1) printf("Konnte nicht in \"kunden.dat\" schreiben\n"); else counter++; } } int main(void) { int wahl; do { printf("\t1: Neuen Kunden eingeben\n\n"); /* printf("\t2: Kunden ausgeben\n\n"); */ printf("\t3: Programmende\n\n"); printf("\tEingabe :> "); do { scanf("%d",&wahl); } while(getchar() != '\n'); switch(wahl) { case 1 : neukunde(); break; /* case 2 : lese(); break; */ case 3 : printf("bye\n"); break; default : printf("Falsche Eingabe!!!\n"); } } while(wahl != 3); return EXIT_SUCCESS; }
Zuerst werden in der Funktion neukunde() die Daten an die Struktur übergeben. Anschließend wird mit
if((fh=creat("kunden.dat",S_IREAD|S_IWRITE)) == -1)
eine Datei namens kunden.dat zum Lesen und Schreiben erzeugt. Jetzt kann mit
else if((write(fh,&k,sizeof(k))) == -1)
in diese Datei über den File-Deskriptor fh von der Adresse struct kunde k mit der Größe der Struktur (sizeof(k)) geschrieben werden. Anschließend wird counter inkrementiert. Ein wenig verwirrend können die if else-Bedingungen sein. Aber bei Korrektheit werden alle drei ausgeführt, solange keine der Bedingungen –1 zurückliefert. Jetzt befindet sich im Verzeichnis, in dem das Programm ausgeführt wird, eine Datei namens kunden.dat. Wird diese Datei mit einem Texteditor geöffnet, könnte man meinen, das Schreiben mit write() hätte nicht geklappt. Aber wie ich bereits erwähnt habe, wird unformatiert in eine Datei geschrieben. Und dies lässt sich nun mal nicht mit einem Texteditor lesen.
Anmerkung |
Das Schreiben mit write() wird über einen Puffercache durchgeführt, bevor wirklich auf die Festplatte, Diskette usw. geschrieben wird. Dieses Delayed Write birgt bei einem Systemabsturz die Gefahr, dass im Cache befindliche Daten nicht physikalisch auf Festplatte oder Diskette geschrieben werden. In diesem Fall können Sie die Datei zum Schreiben im O_SYNC-Modus öffnen. Dieser Modus wartet bei jedem physikalischen Schreibvorgang, bis dieser fertig ist, und liest dann erst wieder Daten ein. Der Modus hat leider den Nachteil, schrecklich langsam zu sein. Für Linux gibt es hier zwei Funktionen, sync() und fsync(), die in diesem Buch allerdings nicht behandelt werden. Linux-User lesen bitte entsprechende Manpages, falls Sie diese Funktionen benötigen. |
Jetzt folgt das Gegenstück zur Funktion write(). Zuerst die Syntax:
int read(int fh, const void *puffer, site_t bytezahl);
Mit der Funktion read() werden bytezahl Bytes aus der Datei mit dem File-Deskriptor fh gelesen. Die Daten werden an derAdresse von puffer abgelegt. Zuvor muss natürlich die Datei mit open() geöffnet werden. Auch hier liefert die Funktion bei einem Fehler –1, ansonsten, wenn alles richtig verlief, die Anzahl gelesener Bytes zurück. Hierzu sehen Sie ein Listing, das eine Datei kopiert:
/* read1.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif #define MAXBYTES 1024 int main(int argc, char *argv[]) { int in,out,count; char buffer[MAXBYTES]; if(argc < 3) { printf("Aufruf: programmname quelldatei zieldatei\n"); return EXIT_FAILURE; } if( (in=open(*++argv ,O_RDONLY)) == -1) printf("Fehler open %s\n", (char *)argv); if( (out=open(*++argv, O_WRONLY | O_TRUNC | O_CREAT)) == -1) printf("Fehler open %s\n", (char *)argv); while( (count = read(in, buffer, MAXBYTES)) ) write(out,buffer,count); close(in); close(out); return EXIT_SUCCESS; }
Damit wird die Datei, die als zweites Argument in der Kommandozeile angegeben wird, in die Datei kopiert, die als drittes Argument angegeben wird.
Jetzt soll das erste Programm aus dem vorigen Abschnitt zu write() mit der Funktion read() ergänzt werden:
/* read2.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif int main(void) { int fh; char puffer[100]; char pufferneu[100]; strcpy(puffer,"Dieser Text steht in \"test.txt\"\n"); if( (fh = open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1) { perror(NULL); return EXIT_FAILURE; } if((write(fh, &puffer, sizeof(puffer))) == -1) { perror("Fehler bei write"); return EXIT_FAILURE; } close(fh); if( (fh = open("test.txt",O_RDONLY)) == -1) { perror(NULL); return EXIT_FAILURE; } if( (read(fh, &pufferneu, sizeof(pufferneu))) == -1) { perror("Fehler bei read"); return EXIT_FAILURE; } printf("%s" ,pufferneu); close(fh); return EXIT_SUCCESS; }
Bis zur Funktion write() ist das so weit nichts Neues für Sie. Mit read() wird hier die Größe von sizeof(pufferneu) Bytes mit dem File-Deskriptor fh in die Adresse von pufferneu gelegt. Das Programm dürfte keinem mehr Kopfzerbrechen bereiten.
Daher soll auch das zweite obige Listing mit der Funktion read() bestückt werden. Schließlich wollen Sie die Daten, die geschrieben wurden, auch wieder lesen können:
/* read3.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #ifdef __unix__ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #include <sys\stat.h> #endif #define MAXADRESSEN 10 #define MAX 30 struct kunde { char name[MAX]; char vorname[MAX]; int kundenummer; char ort[MAX]; char strasse[MAX]; int hausnummer; int vorwahl; int telefonnr; }; struct kunde k[MAXADRESSEN]; static int counter=0; void neukunde(void) { int fh; if(counter==MAXADRESSEN) printf("Kein Speicherplatz mehr frei!!!\n"); else { printf("Name...................: "); fgets(k[counter].name, MAX, stdin); printf("Vorname................: "); fgets(k[counter].vorname, MAX, stdin); k[counter].kundenummer=counter; printf("Ort....................: "); fgets(k[counter].ort, MAX, stdin); printf("Strasse................: "); fgets(k[counter].strasse, MAX, stdin); printf("Hausnummer.............: "); do { scanf("%d",&k[counter].hausnummer); } while(getchar() != '\n'); printf("Vorwahl................: "); do { scanf("%d",&k[counter].vorwahl); } while(getchar() != '\n'); printf("Telefonnummer..........: "); do { scanf("%d",&k[counter].telefonnr); } while(getchar() != '\n'); if((fh=open("kunden.dat",O_CREAT|O_RDWR)) == -1) printf("Konnte\"kunden.dat\" nicht öffnen\n"); else if((write(fh,&k,sizeof(k))) == -1) printf("Konnte nicht in \"kunden.dat\" schreiben\n"); else counter++; } } void lese(void) { int fh; int num; printf("Bitte geben Sie die Kundennummer ein : "); scanf("%d",&num); if( (fh = open("kunden.dat",O_RDONLY)) == -1) { perror("Kann Kundendatei nicht öffnen"); exit(EXIT_FAILURE); } read(fh,&k,sizeof(k)); printf("\n\n"); printf("Name..........%s",k[num].name); printf("Vorname.......%s",k[num].vorname); printf("Kundennummer..%d\n",k[num].kundenummer); printf("Wohnort.......%s",k[num].ort); printf("Strasse.......%s",k[num].strasse); printf("Hausnummer....%d\n",k[num].hausnummer); printf("Vorwahl.......%d\n",k[num].vorwahl); printf("Telefonnum....%d\n",k[num].telefonnr); } int main(void) { int wahl; do { printf("\t1: Neuen Kunden eingeben\n\n"); printf("\t2: Kunden ausgeben\n\n"); printf("\t3: Programmende\n\n"); printf("\tEingabe :> "); do { scanf("%d",&wahl); }while(getchar() != '\n'); switch(wahl) { case 1 : neukunde(); break; case 2 : lese(); break; case 3 : printf("bye\n"); break; default: printf("Falsche Eingabe!!!\n"); } } while(wahl != 3); return EXIT_SUCCESS; }
Das Datenprogramm ist wieder um eine Funktion reicher geworden, nämlich um lese(). Bei dieser wird mit
if((fh=open("kunden.dat", O_RDONLY)) == -1)
die Kundendatei zum Lesen geöffnet und mit
read(fh,&k,sizeof(k));
ausgelesen sowie anschließend auf dem Bildschirm ausgegeben.
Ein paar Zeilen noch zum File-Deskriptor. Die folgende Anwendung des File-Deskriptors ist bekannt:
write(fh, &puffer, sizeof(puffer)); read(fh, &puffer, sizeof(puffer));
Aber statt des File-Deskriptors fh können logischerweise auch Ganzzahlen verwendet werden. Die in Tabelle 16.17 genannten Ziffern sind allerdings fest belegt, da sie vordefinierte Deskriptoren sind.
Dezimalzahl | Bedeutung |
0 |
Standardeingabe (stdin) |
1 |
Standardausgabe (stdout) |
2 |
Standardfehlerausgabe (stderr) |
Ein Listing dazu:
/* deskriptor_nr1.c */ #include <stdio.h> #include <stdlib.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #endif int main(void) { char puffer[100]; read(0, &puffer, sizeof(puffer)); printf("%s",puffer); return EXIT_SUCCESS; }
Mit read(0, &puffer, sizeof(puffer)) wird aus der Standardeingabe (stdin) in die Adresse des Puffers gelesen, also von der Tastatur. Anhand der Ausgabe können Sie auch die Eigenheiten der niedrigeren Ebene erkennen. Hier wird nicht automatisch ein Stringende-Zeichen angehängt, darum müssen Sie sich selbst kümmern. Dasselbe kann auch mit write() auf dem Bildschirm vorgenommen werden:
/* deskriptor_nr2.c */ #include <stdio.h> #include <stdlib.h> #ifdef __unix__ #include <unistd.h> #elif __MSDOS__ || __WIN32__ || _MSC_VER #include <io.h> #endif int main(void) { char puffer[] = "Ich werde im Low-Level-I/O ausgegeben"; write(1, &puffer, sizeof(puffer)); return EXIT_SUCCESS; }
Da der File-Deskriptor manchmal so verwendet wird, sollte dies hier nicht unerwähnt bleiben.
16.26.5 File-Deskriptor positionieren – »lseek()« 

lseek() ist dieselbe Funktion, die bei der höheren Ebene fseek() hieß, und dient zum Verschieben des File-Deskriptors in der geöffneten Datei. Die Syntax von lseek() lautet:
#inlcude <unistd.h> /* für UNIX */ #include <sys/types.h> /* für UNIX */ #inlcude <io.h> /* für MS-DOS/WIN */ long lseek(int fh, long offset, int wie);
Die Datei, in der der File-Deskriptor verschoben werden soll, wird mit dem File-Deskriptor fh angegeben, der natürlich zuvor mit open() geöffnet bzw. erzeugt wurde. Um wie viele Bytes der File-Deskriptor von der Position wie verschoben werden soll, wird mit offset angegeben. Die Angaben von wie sind dieselben wie schon bei fseek(). Tabelle 16.18 zeigt, welche Möglichkeiten zur Verfügung stehen.
wie-Angabe | Beschreibung |
SEEK_SET oder 0 |
Schreib/Lese-Deskriptor vom Dateianfang um offset Bytes versetzen |
SEEK_CUR oder 1 |
Schreib/Lese-Deskriptor von der aktuellen Position um offset Bytes versetzen |
SEEK_END oder 2 |
Schreib/Lese-Deskriptor vom Dateiende um offset Bytes versetzen |
Als Rückgabewert gibt diese Funktion den Wert der aktuellen Position des File-Deskriptors zurück:
long aktuelle_position; aktuelle_position = lseek(fh, 0L, SEEK_CUR);
Bei einem Fehler gibt diese Funktion –1 zurück. lseek() sollte allerdings nicht auf kleiner als 0 geprüft werden, sondern auf –1, da es durchaus sein kann, dass es Gerätedateien gibt, die einen negativen Wert zurückliefern. Weitere Möglichkeiten von lseek() sind:
Deskriptor auf den Dateianfang setzen:
lseek(fh, 0L, SEEK_SET);
Deskriptor um 100 Bytes von der aktuellen Position nach vorn versetzen:
lseek(fh, 100L, SEEK_CUR);
Deskriptor um 10 Bytes von der aktuellen Position zurücksetzen:
lseek(fh, -10L, SEEK_CUR);
Deskriptor auf das letzte Byte setzen (nicht EOF):
lseek(fh, -1L, SEEK_END);
Ein Beispiel zu lseek() kann ich mir sparen, da diese Funktion genauso eingesetzt wird wie fseek(); nur dass anstatt eines Streams hierbei ein File-Deskriptor verwendet wird.
16.26.6 File-Deskriptor von einem Stream – »fileno()« 

Manchmal benötigen Sie von einem offenen Stream den File-Deskriptor. Die Syntax dieser Funktion lautet:
int fileno(FILE *fz);
fileno() ist erforderlich, falls eine Datei mit fopen() geöffnet wurde, um den Stream für Funktionen einzusetzen, die einen File-Deskriptor benötigen (z. B. Funktionen wie dup(), dup2() oder fcntl()). Hier sehen Sie ein Listing dazu:
/* fileno.c */ #include <stdio.h> #include <stdlib.h> #ifdef __unix__ #include <unistd.h> #else #include <io.h> #endif int main(void) { FILE *fz; int fd,fd2; char datei[255]; printf("File-Deskriptoren zu stdin, stdout und stderr : "); printf("%d, %d und %d\n", fileno(stdin),fileno(stdout),fileno(stderr)); printf("Welche Datei wollen Sie öffnen : "); scanf("%s",datei); fz=fopen(datei, "r"); if(!fz) { perror(NULL); return EXIT_FAILURE; } fd = fileno(fz); printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd); fd2=dup(fd); printf("File-Deskriptor, der kopiert wurde, lautet %d\n",fd2); return EXIT_SUCCESS; }
Zu Beginn des Programms werden erst die File-Deskriptoren zu stdin, stdout und stderr ausgegeben, diese sollten immer 0, 1 und 2 sein. Anschließend wird der File-Deskriptor in einer von Ihnen geöffneten Datei ausgegeben. Dieser File-Deskriptor wird jetzt mit der Funktion dup() dupliziert und ebenfalls auf dem Bildschirm ausgegeben.
16.26.7 Stream von File-Deskriptor – »fdopen()« 

Mit der Funktion fdopen() erhalten Sie aus einem File-Deskriptor einen FILE-Zeiger:
#include <stdio.h> FILE *fdopen(int fd, const char *modus);
fdopen() ist das Gegenstück zu fileno(). Als modus, wie die Datei geöffnet wird, können dieselben Modi wie bei der Funktion open() genutzt werden.
fdopen() wird oft auf File-Deskriptoren angewandt, die von Funktionen zurückgegeben werden, die Pipes oder Kommunikationskanäle in Netzwerken einrichten. Das kommt daher, weil einige Funktionen (open(), dup(), dup2(), fcntl(), pipe(), ...) in Netzwerken nichts mit Streams anfangen können und File-Deskriptoren benötigen. Um aber wieder aus Deskriptoren einen Stream (FILE-Zeiger) zu erzeugen, ist die Funktion fdopen() erforderlich. Hierzu ein kurzes Beispiel:
/* fdopen.c */ #include <stdio.h> #include <stdlib.h> #ifdef __linux__ #include <unistd.h> #else #include <io.h> #endif int main(void) { FILE *fz, *fz2; int fd,fd2; char datei[255]; printf("Welche Datei wollen Sie erzeugen: "); scanf("%s",datei); fz=fopen(datei, "w+"); if(!fz) perror(NULL); fd = fileno(fz); printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd); fd2=dup(fd); printf("Der File-Deskriptor, der kopiert wurde %d\n\n",fd2); printf("Wir wollen einen STREAM oeffnen....\n"); fz2 = fdopen(fd2, "w"); if(!fz2) perror(NULL); fprintf(fz,"Dieser Text steht in %s\n",datei); fprintf(fz2,"Dieser Text steht auch in %s\n",datei); fprintf(stdout,"Es wurde etwas in die " "Datei %s geschrieben",datei); return EXIT_SUCCESS; }
Die beiden Funktionen fileno() und fdopen() werden vorwiegend in der Netzwerkprogrammierung eingesetzt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.