Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
Vorwort des Gutachters
1 Einstieg in C
2 Das erste Programm
3 Grundlagen
4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()«
5 Basisdatentypen
6 Operatoren
7 Typumwandlung
8 Kontrollstrukturen
9 Funktionen
10 Präprozessor-Direktiven
11 Arrays
12 Zeiger (Pointer)
13 Kommandozeilenargumente
14 Dynamische Speicherverwaltung
15 Strukturen
16 Ein-/Ausgabe-Funktionen
17 Attribute von Dateien und das Arbeiten mit Verzeichnissen (nicht ANSI C)
18 Arbeiten mit variabel langen Argumentlisten – <stdarg.h>
19 Zeitroutinen
20 Weitere Headerdateien und ihre Funktionen (ANSI C)
21 Dynamische Datenstrukturen
22 Algorithmen
23 CGI mit C
24 MySQL und C
25 Netzwerkprogrammierung und Cross–Plattform-Entwicklung
26 Paralleles Rechnen
27 Sicheres Programmieren
28 Wie geht’s jetzt weiter?
A Operatoren
B Die C-Standard-Bibliothek
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch
Buch: C von A bis Z

C von A bis Z
3., aktualisierte und erweiterte Auflage, geb., mit CD und Referenzkarte
1.190 S., 39,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1411-7
Pfeil 16 Ein-/Ausgabe-Funktionen
Pfeil 16.1 Was ist eine Datei?
Pfeil 16.2 Formatierte und unformatierte Ein-/Ausgabe
Pfeil 16.3 Standard-Streams
Pfeil 16.4 Höhere Ein-/Ausgabe-Funktionen
Pfeil 16.5 Datei (Stream) öffnen – »fopen«
Pfeil 16.5.1 Modus für »fopen()«
Pfeil 16.5.2 Maximale Anzahl geöffneter Dateien – »FOPEN_MAX«
Pfeil 16.6 Zeichenweise lesen und schreiben – »getchar()« und »putchar()«
Pfeil 16.6.1 Ein etwas portableres »getch()«
Pfeil 16.7 Zeichenweise lesen und schreiben – »putc()«/»fputc()« und »getc()«/»fgetc()«
Pfeil 16.8 Datei (Stream) schließen – »fclose()«
Pfeil 16.9 Formatiertes Einlesen/Ausgeben von Streams mit »fprintf()« und »fscanf()«
Pfeil 16.10 Standard-Streams in C
Pfeil 16.10.1 Standard-Streams umleiten
Pfeil 16.11 Fehlerbehandlung von Streams – »feof()«, »ferror()« und »clearerr()«
Pfeil 16.12 Gelesenes Zeichen in die Eingabe zurückschieben – »ungetc()«
Pfeil 16.13 (Tastatur-)Puffer leeren – »fflush()«
Pfeil 16.14 Stream positionieren – »fseek()«, »rewind()« und »ftell()«
Pfeil 16.15 Stream positionieren – »fsetpos()«, »fgetpos()«
Pfeil 16.16 Zeilenweise Ein-/Ausgabe von Streams
Pfeil 16.16.1 Zeilenweise lesen mit »gets()«/»fgets()«
Pfeil 16.16.2 Zeilenweise schreiben mit »puts()«/»fputs()«
Pfeil 16.16.3 Zeilenweise vom Stream einlesen mit »getline()« (nicht ANSI C)
Pfeil 16.16.4 Rezepte für zeilenweises Einlesen und Ausgeben
Pfeil 16.17 Blockweise lesen und schreiben – »fread()« und »fwrite()«
Pfeil 16.17.1 Blockweise lesen – »fread()«
Pfeil 16.17.2 Blockweise schreiben – »fwrite()«
Pfeil 16.17.3 Big Endian und Little Endian
Pfeil 16.18 Datei (Stream) erneut öffnen – »freopen()«
Pfeil 16.19 Datei löschen oder umbenennen – »remove()« und »rename()«
Pfeil 16.19.1 remove()
Pfeil 16.19.2 rename()
Pfeil 16.20 Pufferung einstellen – »setbuf()« und »setvbuf()«
Pfeil 16.20.1 Die Funktion »setbuf()«
Pfeil 16.20.2 Die Funktion »setvbuf()«
Pfeil 16.21 Temporäre Dateien erzeugen – »tmpfile()« und »tmpnam()«
Pfeil 16.21.1 »mkstemp()« – sichere Alternative für Linux/UNIX (nicht ANSI C)
Pfeil 16.22 Fehlerbehandlung
Pfeil 16.22.1 Fehlerausgabe mit »perror()«
Pfeil 16.22.2 Fehlerausgabe mit »strerror()«
Pfeil 16.23 Formatiert in einen String schreiben und formatiert aus einem String lesen – »sscanf()« und »sprintf()«
Pfeil 16.24 Byte- und wide-orientierter Stream
Pfeil 16.25 Ein fortgeschrittenes Thema
Pfeil 16.26 Low-Level-Datei-I/O-Funktionen (nicht ANSI C)
Pfeil 16.26.1 Datei öffnen – »open()«
Pfeil 16.26.2 Datei schließen – »close()«
Pfeil 16.26.3 Datei erzeugen – »creat()«
Pfeil 16.26.4 Schreiben und Lesen – »write()« und »read()«
Pfeil 16.26.5 File-Deskriptor positionieren – »lseek()«
Pfeil 16.26.6 File-Deskriptor von einem Stream – »fileno()«
Pfeil 16.26.7 Stream von File-Deskriptor – »fdopen()«


Rheinwerk Computing - Zum Seitenanfang

16.26 Low-Level-Datei-I/O-Funktionen (nicht ANSI C) Zur nächsten ÜberschriftZur vorigen Überschrift

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



Rheinwerk Computing - Zum Seitenanfang

16.26.1 Datei öffnen – »open()« Zur nächsten ÜberschriftZur vorigen Überschrift

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


Tabelle 16.11 Bearbeitungsmodi für »open()«

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.


Tabelle 16.12 Weitere Bearbeitungsmodi für »open()«

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.


Tabelle 16.13 Bearbeitungsmodi nur für Linux/UNIX

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.


Tabelle 16.14 Bearbeitungsmodi nur für MS-DOS/Windows

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.


Tabelle 16.15 Zugriffsrechte auf eine Datei erteilen (MS-DOS/Windows)

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.


Tabelle 16.16 Zugriffsrechte auf eine Datei erteilen (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);

Rheinwerk Computing - Zum Seitenanfang

16.26.2 Datei schließen – »close()« Zur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

16.26.3 Datei erzeugen – »creat()« Zur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

16.26.4 Schreiben und Lesen – »write()« und »read()« Zur nächsten ÜberschriftZur vorigen Überschrift

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.


Tabelle 16.17 Besetzte Werte für Deskriptoren

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.


Rheinwerk Computing - Zum Seitenanfang

16.26.5 File-Deskriptor positionieren – »lseek()« Zur nächsten ÜberschriftZur vorigen Überschrift

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.


Tabelle 16.18 Bezugspunkt für die Positionierung (gleich wie bei »fseek«)

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.


Rheinwerk Computing - Zum Seitenanfang

16.26.6 File-Deskriptor von einem Stream – »fileno()« Zur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

16.26.7 Stream von File-Deskriptor – »fdopen()« topZur vorigen Überschrift

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.

<< zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: C von A bis Z

 C von A bis Z
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: C/C++






 C/C++


Zum Rheinwerk-Shop: Einstieg in C






 Einstieg in C


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern