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

 << zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch für Linux, Unix und Windows
– 2., aktualisierte und erweiterte Auflage 2006
Buch: C von A bis Z

C von A bis Z
1.116 S., mit CD, Referenzkarte, 39,90 Euro
Galileo Computing
ISBN 3-89842-643-2
gp Kapitel 18 Ein-/Ausgabe-Funktionen
  gp 18.1 Was ist eine Datei?
  gp 18.2 Formatierte und unformatierte Ein-/Ausgabe
  gp 18.3 Streams
  gp 18.4 Höhere Ein-/Ausgabe-Funktionen
  gp 18.5 Datei (Stream) öffnen – fopen
    gp 18.5.1 Modus für fopen()
    gp 18.5.2 Maximale Anzahl geöffneter Dateien – FOPEN_MAX
  gp 18.6 Zeichenweise Lesen und Schreiben – getchar und putchar
    gp 18.6.1 Ein etwas portableres getch()
  gp 18.7 Zeichenweise Lesen und Schreiben – putc/fputc und getc/fgetc
  gp 18.8 Datei (Stream) schließen – fclose
  gp 18.9 Formatiertes Einlesen/Ausgeben von Streams mit fprintf und fscanf
  gp 18.10 Standard-Streams in C
    gp 18.10.1 Standard-Streams umleiten
  gp 18.11 Fehlerbehandlung von Streams – feof, ferror und clearerr
  gp 18.12 Gelesenes Zeichen in die Eingabe zurück-schieben – ungetc
  gp 18.13 (Tastatur-)Puffer leeren – fflush
    gp 18.13.1 Pufferung
  gp 18.14 Stream positionieren – fseek, rewind und ftell
  gp 18.15 Stream positionieren – fsetpos, fgetpos
  gp 18.16 Zeilenweise Ein-/Ausgabe von Streams
    gp 18.16.1 Zeilenweise Lesen mit gets/fgets
    gp 18.16.2 Zeilenweise Schreiben mit puts/fputs
    gp 18.16.3 Zeilenweise Einlesen vom Stream mit getline() (nicht ANSI C)
    gp 18.16.4 Rezepte für zeilenweises Einlesen und Ausgeben
  gp 18.17 Blockweise Lesen und Schreiben – fread und fwrite
    gp 18.17.1 Blockweises Lesen – fread()
    gp 18.17.2 Blockweises Schreiben – fwrite()
    gp 18.17.3 Big-Endian und Little-Endian
  gp 18.18 Datei (Stream) erneut öffnen – freopen
  gp 18.19 Datei löschen oder umbenennen – remove und rename
    gp 18.19.1 remove()
    gp 18.19.2 rename()
  gp 18.20 Pufferung einstellen – setbuf und setvbuf
  gp 18.21 Temporäre Dateien erzeugen – tmpfile und tmpnam
    gp 18.21.1 mkstemp() – Sichere Alternative für Linux/UNIX (nicht ANSI C)
  gp 18.22 Fehlerausgabe mit strerror und perror
  gp 18.23 Formatiert in einem String schreiben und formatiert aus einem String lesen – sscanf und sprintf
  gp 18.24 Fortgeschrittenes Thema
  gp 18.25 Low-Level-Datei-I/O-Funktionen (nicht ANSI C)
  gp 18.26 Datei öffnen – open
  gp 18.27 Datei schließen – close
  gp 18.28 Datei erzeugen – creat
  gp 18.29 Schreiben und Lesen – write und read
  gp 18.30 File-Deskriptor positionieren – lseek
  gp 18.31 File-Deskriptor von einem Stream – fileno
  gp 18.32 Stream von File-Deskriptor – fdopen


Galileo Computing - Zum Seitenanfang

18.17 Blockweise Lesen und Schreiben – fread und fwrite  downtop

Diese beiden Funktionen lassen sich nicht so recht in ein Thema der Datei-E/A einordnen. Weder in den höheren Standardfunktionen (High Level) noch in den niedrigeren Funktionen (Low-Level). Mit fread() und fwrite() wird eine Datei nicht als strukturierte Textdatei und auch nicht als unformatierter Bytestrom betrachtet. Die Dateien werden im Binärmodus bearbeitet und haben eine feste Satzstruktur. Das heißt, die Funktionen fread() und fwrite() erledigen nichts anderes als binäres Lesen und Schreiben ganzer Blöcke.


Hinweis   Die Bezeichnungen »Textmodus« und »Binärmodus« können hierbei recht verwirrend sein. ANSI C erlaubt beide Möglichkeiten, eine Datei zu öffnen. Informationen können somit im Binärmodus oder Textmodus geschrieben oder gelesen werden, eine Textdatei beispielsweise können Sie also im Binärmodus öffnen. Ebenso lässt sich ein Text im Binärmodus abspeichern. Sie können auch die Funktion fgetc() oder getc() verwenden, um eine Datei mit binären Daten zu kopieren.


Das Gute an diesen beiden Funktionen ist ihre einfache Anwendung. Der Nachteil ist aber, dass die Daten der Datei, die fwrite() schreibt, nicht portabel und plattformabhängig sind. Wollen Sie zum Beispiel mit diesen Funktionen Hauptspeicherinhalte direkt in eine Datei schreiben, könnten aufgrund eines anderen Alignments Probleme auftreten. Dies kommt daher, weil ein Member-Alignment in anderen Strukturen eine andere Byte-Reihenfolge bei Ganzzahlen oder eine unterschiedliche interne Darstellung von Fließkommazahlen haben könnte. Dieses Problem kann schon bei unterschiedlichen Compilern auftreten! Ist es also wichtig, dass die Daten auch auf anderen Systemen gelesen werden können, haben Sie folgende zwei Möglichkeiten:

gp  Die bessere Lösung wäre es, eine einfache ASCII-Textdatei zu verwenden, die mit fprintf() geschrieben und mit fscanf() gelesen wird. Ähnlich wird übrigens auch bei Netzwerkprotokollen vorgegangen. Zwar sind Textdateien meistens größer, und die Ein-/Ausgabe läuft ein wenig langsamer ab, aber der Vorteil, die einfachere Handhabung der Daten, macht diese Nachteile wieder wett.
gp  Sie überprüfen, ob es sich dabei um eine Big-Endian oder Little-Endian-Maschine handelt.

Diese Funktionen ergänzen das Sortiment der Lese- und Schreibfunktionen hervorragend. Denn mit fgets() und fputs() lassen sich, wegen der besonderen Bedeutung der Zeichen '\0' und '\n' in ihnen, schlecht ganze Blöcke von Daten lesen bzw. schreiben. Ebenso ist es nicht sinnvoll, Daten Zeichen für Zeichen zu verarbeiten, wie dies bei den Funktionen fputc() und fgetc() geschieht. Außerdem ist es nahe liegend, dass sich diese beiden Funktionen hervorragend zum Lesen und Schreiben von Strukturen eignen.


Galileo Computing - Zum Seitenanfang

18.17.1 Blockweises Lesen – fread()  downtop

Hier die Syntax der Funktion:

size_t fread(void *puffer, size_t blockgroesse,
             size_t blockzahl, FILE *datei);

size_t ist ein primitiver Systemdatentyp für die Größe von Speicherobjekten. fread() liest blockzahl-Speicherobjekte, von denen jedes die Größe von blockgroesse Bytes hat, aus dem Stream FILE *datei, der zuvor geöffnet wurde, in die Adresse von puffer. Für puffer muss dementsprechend viel Platz zur Verfügung stehen.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 18.6   Blockweises Lesen mit fread()

Es sei eine Datei namens »wert.dat« mit Inhalten von Integerwerten gegeben. Werden z.B. die ersten zehn Werte benötigt, sieht der Quellcode folgendermaßen aus:

/* fread.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   int puffer[10];
   FILE *quelle;
   int i;
   quelle = fopen("wert.dat", "r+b");
   if(quelle != NULL)
      fread(&puffer, sizeof(int), 10, quelle);
   for(i = 0; i < 10; i++)
      printf("Wert %d = %d\n", i, puffer[i]);
 return EXIT_SUCCESS;
}

Folgende Zeile soll wie oben interpretiert werden:

fread(&puffer, sizeof(int), 10, quelle);

fread() liest 10 Datenobjekte mit der Größe von je sizeof(int) Bytes aus dem Stream quelle in die Adresse von puffer.

Ein wenig undurchsichtig dürfte der Parameter void *puffer bei fread() und fwrite() erscheinen. Mit dem void-Zeiger haben Sie den Vorteil, dass diesem Parameter ein Zeiger beliebigen Datentyps übergeben werden kann. In Kapitel 14, Zeiger, wurde dies bereits durchgenommen.


Galileo Computing - Zum Seitenanfang

18.17.2 Blockweises Schreiben – fwrite()  downtop

Jetzt zur Funktion fwrite():

size_t fwrite(const void *puffer,size_t blockgroesse,
              size_t blockzahl, FILE *datei);

Mit fwrite() werden blockzahl-Speicherobjekte, von denen jedes blockgroesse Bytes groß ist, von der Adresse puffer in den Stream datei geschrieben:

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 18.7   Blockweise Schreiben mit fwrite()

Wieder ein Beispiel:

struct {
   char name[20];
   char vornam[20];
   char wohnort[30];
   int alter;
   int plz;
   char Strasse[30];
} adressen;
FILE *quelle;
strcpy(adressen.name, "George W.");
strcpy(adressen.vornam, "Bush");
strcpy(adressen.wohnort, "Washington D.C");
adressen.alter = 55;
adressen.plz = 23223;
...
if((quelle=fopen("adres.dat", "w+b")) == NULL)
...
fwrite(&adressen, sizeof(struct adressen), 1, quelle);

Hier wird mit fwrite() aus der Adresse adressen ein Speicherobjekt mit der Größe von sizeof(struct adressen) Bytes in den Stream quelle geschrieben.

Als Beispiel zu den Funktionen fread() und fwrite() folgt ein kleines Adressenverwaltungsprogramm ohne irgendwelche besonderen Funktionen, um nicht vom eigentlichen Thema abzulenken:

/* fread_fwrite.c */
#include <stdio.h>
#include <stdlib.h>
struct {
   char vorname[20];
   char nachname[30];
   char strasse[30];
   char hausnummer[5];
   char plz[7];
   char ort[30];
   char sternzeichen[30];
   char alter[3];
} adressen;
void speichern(void) {
   FILE *save = fopen("adressen.dat","r+b");
   if( NULL == save ) {
      save = fopen("adressen.dat","w+b");
      if( NULL == save ) {
         fprintf(stderr,"Kann \"adressen.dat\" nicht öffnen!\n");
         return EXIT_FAILURE;
      }
   }
   /* FILE-Zeiger save auf das Ende der Datei setzen */
   fseek(save, 0, SEEK_END);
   /* Wir schreiben eine Adresse ans Ende von "adressen.dat" */
   if(fwrite(&adressen, sizeof(adressen), 1, save) != 1) {
      fprintf(stderr, "Fehler bei fwrite...!!!\n");
      return EXIT_SUCCESS;
   }
   /* Wir geben unseren FILE-Zeiger wieder frei */
   fclose(save);
}
void ausgabe(void) {
   FILE *output = fopen("adressen.dat","r+b");
   if( NULL == output ) {
      fprintf(stderr,"Kann \"adressen.dat\" nicht öffnen!\n");
      return EXIT_FAILURE;
   }
   /* Wir lesen alle Adressen aus "adressen.dat" */
   while(fread(&adressen, sizeof(adressen), 1, output) == 1) {
      printf("Vorname...........: %s",adressen.vorname);
      printf("Nachname..........: %s",adressen.nachname);
      printf("Strasse...........: %s",adressen.strasse);
      printf("Hausnummer........: %s",adressen.hausnummer);
      printf("Postleitzahl......: %s",adressen.plz);
      printf("Ort...............: %s",adressen.ort);
      printf("Sternzeichen......: %s",adressen.sternzeichen);
      printf("Alter.............: %s",adressen.alter);
      printf("\n\n");
   }
   fclose(output);
}
void eingabe(void) {
   printf("Vorname...........:");
   fgets(adressen.vorname, sizeof(adressen.vorname), stdin);
   printf("Nachname..........:");
   fgets(adressen.nachname, sizeof(adressen.nachname),stdin);
   printf("Strasse...........:");
   fgets(adressen.strasse, sizeof(adressen.strasse), stdin);
   printf("Hausnummer........:");
   fgets(adressen.hausnummer,sizeof(adressen.hausnummer),stdin);
   printf("Postleitzahl......:");
   fgets(adressen.plz, sizeof(adressen.plz), stdin);
   printf("Ort...............:");
   fgets(adressen.ort, sizeof(adressen.ort), stdin);
   printf("Sternzeichen......:");
   fgets(adressen.sternzeichen,sizeof(adressen.sternzeichen),
      stdin );
   printf("Alter.............:");
   fgets(adressen.alter, sizeof(adressen.alter), stdin);
   speichern();
}
int main(void) {
   int wahl;
   do {
      printf("Was wollen Sie machen:\n\n");
      printf("-1- Neuen Datensatz hinzufuegen\n");
      printf("-2- Alle Datensaetze ausgeben\n");
      printf("-3- Programm beenden\n\n");
      printf("Ihre Auswahl : ");
      do {
         scanf("%d",&wahl);
      } while(getchar() != '\n');
      switch(wahl) {
         case 1 : eingabe();        break;
         case 2 : ausgabe();        break;
         case 3 : printf("...Programm wird beendet\n");
                  break;
         default: printf(">>%d<< ???\n",wahl);
      }
   } while(wahl != 3);
   return EXIT_SUCCESS;
}

Zuerst wurde eine Struktur mit dem Namen adressen deklariert. In der Funktion speichern() wird, falls vorhanden, die Datei »adressen.dat« geöffnet. Ansonsten wird diese Datei erstellt:

   FILE *save = fopen("adressen.dat","r+b");
   if( NULL == save ) {
      save = fopen("adressen.dat","w+b");
      if( NULL == save ) {

Gleich darauf wird der Stream save an das Ende der Datei »adressen.dat« positioniert:

fseek(save, 0, SEEK_END);

Jetzt kann der Adressensatz in die Datei geschrieben werden:

if(fwrite(&adressen, sizeof(adressen), 1, save) != 1)

Nochmals eine Erklärung von fwrite():

gp  &adressen – Anfangsadresse der Struktur adressen, welche am Programmbeginn deklariert und in der Funktion eingabe() mit Werten initialisiert wurde.
gp  sizeof(struct adressen) – Größe (Blockgröße) in Byte, wie viel auf einmal in den Stream save geschrieben werden soll.
gp  1 – Anzahl der Blöcke von der Größe sizeof(adressen), welche in den Stream save geschrieben werden.
gp  save – Stream, der zuvor geöffnet wurde, und in dem geschrieben wird.

Diese Anweisung wurde in eine if-Bedingung gepackt, die eine Fehlerausgabe vornimmt, falls weniger als ein Block geschrieben wird. Mit der Funktion ausgabe() wird diese Datei jetzt über fread() blockweise ausgelesen und der Inhalt auf dem Bildschirm ausgegeben:

while(fread(&adressen, sizeof(adressen), 1, output) == 1)

Es wird so lange ausgelesen, bis kein ganzer Block der Größe sizeof(adressen) mehr vorhanden ist. Auch hierzu eine genauere Erläuterung von fread():

gp  &adressen – hierhin wird der Block der Größe sizeof(adressen), auf die der FILE-Zeiger output zeigt, »geschoben«. Natürlich handelt es sich auch hier um die Struktur adressen, welche durch den Aufruf von fread() mit dementsprechenden Werten initialisiert wird.
gp  sizeof(adressen) – Größe des Blocks, der gelesen werden soll.
gp  1 – Anzahl der Blöcke, die gelesen werden.
gp  output – Stream, aus dem gelesen wird.

Galileo Computing - Zum Seitenanfang

18.17.3 Big-Endian und Little-Endian  toptop

Es wurde bereits erwähnt, dass die Funktionen fread() und fwrite() nicht portabel und somit plattformabhängig sind. Sollten Sie also Programme schreiben wollen, die auf den verschiedensten Systemen laufen sollen, bleibt Ihnen nur die Wahl, diese Funktionen nicht zu verwenden, oder Sie finden heraus, auf welchem System genau sie laufen sollen. Unterschieden werden die Systeme dabei nach Little-Endian und Big-Endian. Little-Endian und Big-Endian sind zwei Methoden, wie die einzelnen Bytes im Speicher angeordnet sind. Little-Endian und Big-Endian unterscheiden sich durch die Anordnung des most significant byte und des least significant byte. Bei einer Word-Größe der CPU von vier Bytes wird das rechte Ende als least significant byte und das linke Ende als most significant byte bezeichnet. Das least significant byte stellt dabei die niedrigeren Werte und das most significant byte die größeren Werte in einem Word dar. Als Beispiel dient jetzt folgende Hex-Zahl:

22CCDDEE

Auf den unterschiedlichen Systemen wird diese Hex-Zahl im Speicher folgendermaßen abgelegt:


Tabelle 18.7   Little-Endian und Big-Endian im Vergleich

Adresse 0x12345 0x12346 0x12347 0x12348
Big-Endian 22 CC DD EE
Little-Endian EE DD CC 22

Um jetzt herauszufinden, auf was für einem System das Programm ausgeführt wird, müssen Sie diese Hex-Zahl in einem Speicher einer Word-Größe schreiben und das erste Byte mithilfe von Bit-Operationen überprüfen. Hier das Listing dazu:

/* endian.c */
#include <stdio.h>
#include <stdlib.h>
typedef unsigned int  WORD;
typedef unsigned char BYTE;
int main(void) {
   /* Word in den Speicher schreiben */
   WORD Word = 0x22CCDDEE;
   /* Zeiger auf ein Byte */
   BYTE *Byte;
   /* Word-Zeiger auf Byte-Zeiger casten */
   Byte = (BYTE *) &Word;
/* Speicherinhalt nach Adressen von links nach rechts
 * ausgeben.
 * byte[0]byte[1]byte[2]byte[3]
 * 22     CC     DD     EE      Speicherinhalt bei Little-Endian
 * EE     DD     CC     22      Speicherinhalt bei Big-Endian
 */
   /* Ist Byte[0] == 11 */
   if(Byte[0] == ((Word >>  0) & 0xFF))
      printf("Little-Endian Architecture\n");
   /* oder ist Byte[0] == CC */
   if(Byte[0] == ((Word >> 24) & 0xFF))
      printf("Big-Endian Architecture\n");
   return EXIT_SUCCESS;
}

Mit

if(Byte[0] == ((Word >>  0) & 0xFF))

werden die ersten acht Bit (ein Byte) mithilfe einer Maske (FF == 256 == 1Byte) gezielt getestet. Werden bei dem Ausdruck ((Word >> 0) & 0xFF)) praktisch keine Bits auf 0 gesetzt, und stimmt danach der ausgewertete Ausdruck mit Byte[0] überein, haben Sie ein Little-Endian-System. Bei der zweiten Bedingung ist es dasselbe, nur wird dabei das vierte (24. bis 32. Bit) Byte, verwendet. Zu den Little-Endian-Systemen gehören z.B.:

gp  Intel CPUs
gp  DEC Alpha
gp  VAX

Und einige Big-Endian-Systeme:

gp  Motorola MC68000 (Amiga, Atari)
gp  SPARC CPUs (SUN)
gp  IBM PowerPC

Einen faden Nachgeschmack hat diese Methode allerdings dann doch. Jetzt wissen Sie zwar, ob es sich um ein Little- oder Big-Endian-System handelt, aber jetzt müssen Sie sich dennoch selbst darum kümmern, dass die einzelnen Bytes richtig gelesen und geschrieben werden. Damit ist gemeint, Sie müssen die Bits selbst verschieben. Aber dies ist ein Thema, dass den Rahmen sprengen und weit über den Titel dieses Buchs hinausgehen würde.

 << zurück
  
  Zum Katalog
Zum Katalog: C von A bis Z
C von A bis Z
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Shell-Programmierung






 Shell-Programmierung


Zum Katalog: Linux-UNIX-Programmierung






 Linux-UNIX-Programmierung


Zum Katalog: C/C++






 C/C++


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Reguläre Ausdrücke






 Reguläre Ausdrücke


Zum Katalog: Linux






 Linux


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2006
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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de