Was währe eine Programmiersprache, ohne das Daten auf einen Datenträger gespeichert werden können. In diesem sehr Umfangreichen Kapitel geht es Ausschließlich um den Fluss der Daten. Damit ist die Eingabe und Ausgabe von Daten gemeint. C C++ C/C++ Datei Stream Eingabe Ausgabe fopen fgets fputs fread fwrite fgetc fputc gets puts sscanf sspintf ... Datei Stream Eingabe Ausgabe fopen fgets fputs fread fwrite fgetc fputc gets puts sscanf sspintf ... Kapitel 19: Datei Ein- Ausgabe

19.16. Zeilenweise Ein-/Ausgabe            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

19.16.1 Zeilenweise Lesen mit gets/fgets
Nun folgen Funktionen zum zeilenweisen Lesen und Schreiben von einem oder in einen Stream. Zuerst die Funktionen zum Lesen:

#include <stdio.h>

char *gets(char *puffer);
char *fgets(char *puffer, int n, FILE *datei);

Mit fgets() wird zeilenweise vom Stream datei bis zum nächsten Newline-Zeichen gelesen. Die gelesene Zeile befindet sich in der Adresse von puffer mit dem Newline-Zeichen '\n' und dem abschließenden '\0'-Zeichen. Mit gets() können Sie ebenso zeilenweise einlesen, allerdings nur von der Standardeingabe (stdin). Beispielsweise:

#include <stdio.h>

int main()
{
   char name[20];
   printf("Bitte geben Sie Ihren Namen ein : ");
   gets(name);  /* Gefährlich */
   printf("Hallo %s\n",name);
   return 0;
}

Auf Linux/UNIX wird der Compiler bei diesem Programm vernünftigerweise eine Warnung ausgeben. Die Warnung, diese Funktion nicht zu verwenden, ist in Ordnung, aber vielleicht sollten gerade für Anfänger auch noch der Grund und die Alternativen bei der Fehlermeldung mit angegeben werden.

Da die Funktion gets() nicht die Anzahl der einzugebenden Zeichen überprüft, kann dies zu einem Pufferüberlauf (Buffer Overflow) führen. Deshalb sollten Sie auf keinen Fall gets(), sondern die Funktion fgets() verwenden. Wenn Sie die Syntax von fgets() betrachten, bemerken Sie, dass sich darin außer der Zieladresse, in der die Daten eingelesen werden, zusätzlich ein Stream (FILE Zeiger) und ein Integer-Wert befinden, der die Anzahl der einzulesenden Zeichen festlegt. Mit fgets werden somit n Zeichen oder bis zum nächsten Newline ('\n') aus dem Stream in die Adresse von puffer gelesen. Wobei der Stream eine beliebig geöffnete Datei oder auch die Standardeingabe (stdin) sein kann. Hierzu das vorige Beispiel mit fgets():

#include <stdio.h>
#define MAX 20

int main()
{
   char name[MAX];
   printf("Bitte geben Sie Ihren Namen ein : ");
   fgets(name, MAX, stdin);
   printf("Hallo %s",name);
   return 0;
}

Sollten hier mehr als 20 Zeichen eingegeben werden, läuft das Programm trotzdem für immer anstandslos. Es werden 20 Zeichen bzw. 18 darstellbare Zeichen + '\n' + '\0' an den String name übergeben. Ein Vorteil ist, dass mit fgets() nicht nur von stdin gelesen werden kann, sondern auch von einem beliebigen Stream. Hier ein Beispiel, wie Sie mit fgets() zeilenweise aus einer Datei lesen können:

#include <stdio.h>
#include <stdlib.h>
#define ZEILENLAENGE 80

int main()
{
   FILE *quelle;
   char puffer[ZEILENLAENGE];
   char name[20];

   printf("Welche Datei wollen Sie zum Lesen öffnen: ");
   scanf("%s",name);
   if((quelle=fopen(name,"r")) == NULL)
      {
         fprintf(stderr,"Kann %s nicht oeffnen\n",name);
         exit (0);
      }

   while(fgets(puffer,ZEILENLAENGE,quelle))
      fputs(puffer,stdout);
   return 0;
}

Weil beim Einlesen vom Stream der Standardeingabe (stdin) mit fgets() auch das '\n'-Zeichen mit eingelesen wird, verwenden einige Programmierer, sei es aus Faulheit oder mangelndem Wissen, die Funktion gets(), obwohl sie wissen, dass sie diese Funktion nicht verwenden sollten. Häufig haben diese Programmierer Probleme mit dem Newline-Zeichen am Ende von Stringvergleichen, wie das folgende Beispiel zeigt:

#include <stdio.h>
#include <string.h>
#define PASSWORT "Schiller"
#define MAX 10

int main()
{
   char pswd[MAX];
   printf("Passwort: ");
   fgets(pswd, MAX, stdin);

   if(strcmp(PASSWORT, pswd) == 0)
      printf("Willkommen\n");
   else
      printf("Passwort falsch\n");
   return 0;
}

Auch wenn hier der Benutzer das richtige Passwort eingibt, schlägt der Stringvergleich fehl, weil fgets() das Newline-Zeichen mit einliest. Dieses Problem lässt sich mit ein paar Zeilen Code beheben:

#include <stdio.h>
#include <string.h>
#define PASSWORT "Schiller"
#define MAX 10

void chomp(char *str)
{
   size_t p=strlen(str);
   /* \n mit \0 überschreiben */
   str[p-1]='\0';
}

int main()
{
   char pswd[MAX];
   printf("Passwort: ");
   fgets(pswd, MAX, stdin);
   /* … letztes Zeichen vor \0 entfernen */
   chomp(pswd);

   if(strcmp(PASSWORT, pswd) == 0)
      printf("Willkommen\n");
   else
      printf("Passwort falsch\n");
   return 0;
}

Die Funktion chomp() nimmt nichts anderes vor, als das letzte Zeichen vor dem Terminierungszeichen '\0' zu entfernen. Dabei wird die Anzahl der Zeichen mit der Funktion strlen() gezählt. Zieht man von diesem Wert eins ab und verwendet ihn als Indexzähler mit dem Indizierungsoperator, befinden Sie sich ein Zeichen vor '\0'.

19.16.2 Zeilenweise Schreiben mit puts/fputs
Mit puts() wird eine ganze Zeile auf dem Bildschirm (stdout) ausgegeben. Außerdem gibt puts() am Ende der Zeichenkette noch ein '\n'-Zeichen mit aus, die Funktion fputs() macht dies hingegen nicht. Im Gegensatz zu puts(), womit Sie nur auf die Standardausgabe (stdout) schreiben können, verwendet fputs(), wie schon fgets(), einen beliebig offenen Stream, in den geschrieben wird. Als Stream ist eine Datei zulässig, die mit einem Schreibmodus geöffnet wurde, oder auch die Standardausgabe (stdout). Hier die Syntax der beiden Funktionen:

#include <stdio.h>

int puts(const char *puffer);
int fputs(const char *puffer, FILE *datei);
Auch hierzu ein kleines Beispiel:
#include <stdio.h>
#include <stdlib.h>
#define ZEILENLAENGE 80

int main()
{
   FILE *quelle,*kopie;
   char puffer[ZEILENLAENGE];
   char name[20];

   printf("Welche Datei wollen Sie zum Lesen öffnen: ");
   scanf("%s",name);

   if((quelle=fopen(name,"r")) == NULL)
      {
         fprintf(stderr,"Kann %s nicht oeffnen\n",name);
         exit (0);
      }

   if((kopie=fopen("kopie.txt","w")) == NULL)
      {
         fprintf(stderr,"Kann kopie.txt nicht oeffnen\n");
         exit (1);
      }

   while(fgets(puffer,ZEILENLAENGE,quelle))
      {
         fputs(puffer,kopie);
         puts(puffer);
      }
   return 0;
}

fputs() wird hier eingesetzt, um den Puffer, der mit fgets() ausgelesen wurde, in eine Datei namens "kopie.txt" zu schreiben. puts() hingegen gibt alles auf dem Bildschirm aus. Somit wird eine Zeile in die Datei "kopie.txt" geschrieben und dasselbe gleich nochmals auf den Bildschirm ausgegeben.

19.16.3 Zeilenweise Einlesen vom Stream mit getline() (nicht ANSI C)
Den Benutzern des GNU-gcc Compilers sei noch die Funktion getline() ans Herz gelegt. Sie gehört zwar nicht zum Umfang von ANSI C, jedoch wird in dem "The GNU C Library Reference Manual" unter www.gnu.org explizit darauf verwiesen. Warum diese Funktion so besonders ist, wird im Anschluss erläutert.

Die Funktion getline() kann als Ersatz für die E/A-Funktion fgets() verwendet werden oder noch allgemeiner - für das Einlesen einer Zeile von einem Stream. Zunächst aber die Syntax der Funktion, welche in <stdio.h> deklariert ist:

ssize_t getline (char **lineptr, size_t *n, FILE *stream) 

Die Funktion liest eine Zeile inklusive dem newline ('\n') und dem Stringende-Zeichen ('\0') in einen Puffer ein und speichert die Adresse des Puffers in *lineptr.

Bevor Sie getline() aufrufen, sollten Sie in *lineptr die Adresse eines zuvor mit malloc() allokierten Puffers der Länge *n Bytes bereitstellen.

Jetzt aber der eigentliche Clou an der Sache: Ist der übergebene Puffer groß genug, erhalten Sie in etwa den Zustand, den Sie auch mit der Funktion fgets() erreichen können. Ist dies aber nicht der Fall, verhält sich getline() nicht wie fgets() und hört bei Überschreitung der angegebenen Puffergröße einfach auf, die Zeile einzulesen. Vielmehr wird der Puffer innerhalb der Funktion auf die erforderliche Größe mit realloc() angepasst. Wenn Sie sich dieses Szenario nun noch mit der Funktion gets() vorstellen, ist das eigenständige Kürzen der einzulesenden Zeile, wie es bei fgets() geschehen würde, noch das geringere Übel.

Das war aber noch nicht alles. Wird *lineptr vor dem Aufruf mit einem NULL-Zeiger initialisiert und *n auf 0 gesetzt, übernimmt getline() die Bereitstellung des Speichers für die Zeile vollkommen selbstständig, und Sie müssen sich um nichts weiter kümmern.

Die Funktion gibt die Anzahl der eingelesenen Zeichen inklusive des Zeilen-Trennzeichens ('\n'), aber ohne das abschließende Terminierungszeichen ('\0') zurück - bei einem Fehler oder bei EOF erhalten Sie von getline() -1. Wenn Sie getline() verwenden wollen, müssen Sie die Konstante _GNU_SOURCE vor allen include-Anweisungen definieren. Laut der GNU-Dokumentation ist diese Funktion der empfohlene Weg, Zeilen vom Stream zu lesen. Sie gilt als die sicherste ihrer Art. In diesem Zusammenhang sei aber nochmals auf das "The GNU C Library Reference Manual" verwiesen.

Hier ein einfaches Beispiel für den Fall, dass Sie die Bereitstellung des Puffers vollständig getline() überlassen wollen:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void)
{
   FILE *fd;
   /* Bitte die Datei und event. den Pfad anpassen */
   char *datei="/home/user/testdatei.txt";
   int nRet;
   size_t *t = malloc(0);

   char **gptr = (char **)malloc(sizeof(char*));
   *gptr = NULL;

   if ( (fd = fopen(datei,"r")) == NULL)
      {
         fprintf(stderr,"\nKonnte Datei %s nicht öffnen!",datei);
         exit(1);
      }
   while( (nRet=getline(gptr, t, fd)) > 0)
      fputs(*gptr,stdout);
  return 0;
}

19.16.4 Rezepte für zeilenweises Einlesen und Ausgeben
Es folgen jetzt einige nützliche Listings, die häufig zum zeilenweisen Einlesen und Ausgeben benötigt werden. Beispiele, die auf Probleme wie die folgenden abheben:

Wie kann ich die n-te Zeile auslesen?

#include <stdio.h>
#include <stdlib.h>
#define BUF 255

char temp[BUF];
char puffer[BUF];

/*Auslesen der n-ten Zeile*/
char *getsline_number(int n, FILE *file)
{
   int i;
   for(i=0; i<n-1; i++)
      if(fgets(temp, BUF, file) == NULL)
         /*Bis zur n-ten Zeile lesen*/
         return NULL; /*Zeile scheint nicht zu existieren*/
   /*Stream ist jetzt in der n-ten Zeile*/
   if(fgets(puffer,BUF,file) == NULL)
       return NULL; /*Zeile scheint nicht zu existieren*/
   return puffer; /*Zeile an Aufrufer zurück*/
}

int main(int argc, char **argv)

{
   FILE *f;
   unsigned int line;
   char *linenr;

   if(argc < 2)
      {
         fprintf(stderr, "Verwendung : %s datei\n",*argv);
         exit(0);
      }
   f=fopen(argv[1],"r");
   if(f == NULL)
      {
         printf("Fehler beim Öffnen");
         exit(0);
      }
   printf("Welche Zeile wollen Sie lesen : ");
   scanf("%d",&line);

   linenr=getsline_number(line, f);
   if(linenr == NULL)
      {
         fprintf(stderr, "Fehler beim Lesen der"
                         " %d-ten Zeile??\n",line);
         exit(0);
      }
   printf("Zeile %d : %s\n",line,linenr);
   return 0;
}

Wie kann ich von Zeile n1 bis n2 lesen?

#include <stdio.h>
#include <stdlib.h>
#define BUF 255

char temp[BUF];
char puffer[BUF];
int i;  /*Zeilenzaehler*/

/*Lesen von Zeile n1 bis Zeile n2*/
char *getsline_number(int n1,int n2, FILE *file)
{
   for(i=0; i<n1-1; i++)
   /*Bis zur n1-ten Zeile lesen*/
      if(fgets(temp, BUF, file) == NULL)
        return NULL; /*Zeile scheint nicht zu existieren*/
   /*Jetzt beginnt das eigentliche Lesen*/
   printf("\n\n");
   for(i=n1; i<=n2; i++)
      {
         if(fgets(puffer,BUF,file)==NULL)
            /*Stream ist jetzt in der n-ten Zeile*/
            return NULL;/*Zeile scheint nicht zu existieren*/
         printf("Zeile %d : %s",i,puffer);
      }
}

int main(int argc, char **argv)
{
   FILE *f;
   int line1,line2;
   char *linenr;

   if(argc < 2)
      {
         fprintf(stderr, "Verwendung : %s datei\n",*argv);
         exit(0);
      }
   f=fopen(argv[1],"r");
   if(f == NULL)
      {
         printf("Fehler bei fopen()...\n");
         exit(0);
      }
   printf("von Zeile wollen Sie lesen : ");
   scanf("%d",&line1);
   printf("bis Zeile wollen Sie lesen : ");
   scanf("%d",&line2);

   if(line2 < line1)
      {
         fprintf(stderr, "bis Zeile kann nicht "
                         "grösser sein, wie von Zeile!\n");
         exit(0);
      }
   linenr=getsline_number(line1,line2, f);
   if(linenr == NULL)
      {
         fprintf(stderr, "Fehler beim Lesen "
                         "der %d-ten Zeile??\n",i);
         exit(0);
      }
   printf("\n");
   return 0;
}

Wie kann ich alle Zeilen ausgeben, die eine bestimmte Stringfolge enthalten?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUF 255

int main(int argc, char **argv)
{
   FILE *f;
   char searchstring[BUF];
   char puffer[BUF];
   int counter=1;

   if(argc < 2)
      {
         fprintf(stderr, "Verwendung : %s datei\n",*argv);
         exit(0);
      }
   f=fopen(argv[1],"r");
   if(f == NULL)
      {
         printf("Fehler bei fopen()...");
         exit(0);
      }
   printf("Wonach suchen Sie in %s : ",argv[1]);
   scanf("%s",searchstring);
   printf("\n");

   while( fgets(puffer, BUF, f) != NULL )
      {
         if(strstr(puffer,searchstring) != 0)
            printf("Zeile %d : %s",counter,puffer);
         counter++;
      }
   printf("\n");
   return 0;
}

Der Nachteil an diesem Beispiel ist, dass strstr() praktisch alle Stringfolgen ausgibt. Suchen Sie beispielsweise nach der Stringfolge "int", dann gibt strstr() auch "wahr" aus, wenn die Folge printf, fprintf, Lint, Mint … lautet. Wenn dies so gewollt ist, dann ist es in Ordnung. Falls nicht, muss mit strtok() ein Worttrenner eingebaut werden.

Wie kann ich alle Zeilen ausgeben, die ein bestimmtes Wort enthalten?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF 255

/* Anpassen nach Bedarf...*/
char trennzeichen[] = ".;,:\"\' ";

int main(int argc, char **argv)
{
   FILE *f;
   char searchstring[BUF];
   char puffer[BUF],puffer_bak[BUF];
   int counter=1;
   char *wort;

   if(argc < 2)
      {
         fprintf(stderr, "Verwendung : %s datei\n",*argv);
         exit(0);
      }
   f=fopen(argv[1],"r");
   if(f == NULL)
      {
         printf("Fehler bei fopen()...");
         exit(0);
      }
   printf("Wonach suchen Sie in %s : ",argv[1]);
   scanf("%s",searchstring);
   printf("\n");

   while(fgets(puffer, BUF, f) != NULL)
      {
         strcpy(puffer_bak,puffer);
         wort=strtok(puffer,trennzeichen);
         while(wort != NULL)
           {
             if(strcmp(wort,searchstring) == 0)
                printf("Zeile %d : %s",counter,puffer_bak);
             wort=strtok(NULL,trennzeichen);
           }
         counter++;
      }
   printf("\n");
   return 0;
}

Suchen und Ersetzen in einer Textdatei (nur ganze Worte, keine Teilstrings)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define STRING_MAX 8192

int wort_begrenzer(char c)
{
   return (c == ' ' || c == '\n' || c == '\t' || c == '\0' ||
       c == '.' || c == ',' || c == ';' || c == '!' ||c == '?');
}

/* Newline von fgets() entfernen */
void chomp(char *str)
{
 size_t p=strlen(str);
 str[p-1]='\0';
}


int main(int argc, char **argv)
{
   FILE *file, *copy;
   char alt_string[STRING_MAX+1],neu_string[STRING_MAX+1],
        such_wort[STRING_MAX+1],replace_wort[STRING_MAX+1],
        *zgr;
   char puffer[STRING_MAX+1];
   int such_wortlen, i=0,w;
   size_t len=0;

   if(argc < 2)
      {
         fprintf(stderr,"Verwendung: %s datei\n",*argv);
         exit(0);
      }
   file=fopen(argv[1],"r"); /*Datei zum Lesen öffnen*/
   copy=fopen(argv[1],"r+"); /*Datei zum Schreiben öffnen*/
   if(file == NULL || copy == NULL)
      {
         printf("Fehler bei fopen()...\n");
         exit(0);
      }
   alt_string[0]='\0';
   /*Kompletten String in alt_string legen..... */
   /*Bitte ggf. selbst durch dynamische Speicherverwaltung
     genügend Platz schaffen! */
   while( (fgets(puffer,STRING_MAX+1,file)) != NULL )
     {
       len += strlen(puffer)+1;
       if(len < STRING_MAX)
         strcat(alt_string, puffer);
       else
          {
             printf("Puffergroesse ueberschritten!\n");
             break;
          }
     }
   neu_string[0]='\0';

   printf("Welches Wort wollen Sie ersetzen : ");
   fgets(such_wort, STRING_MAX, stdin );
   chomp(such_wort);

   such_wortlen=strlen(such_wort); /*Länge des Suchwortes*/
   for(w=0; w < such_wortlen; w++)
   /*Nach Wortbegrenzern duchlaufen...*/
      if(wort_begrenzer(such_wort[w]))
         {
            printf("Keine Wortbegrenzer im Suchwort!!!\n");
            exit(0);
         }

   printf("Durch welches Wort wollen Sie ersetzen : ");
   fgets(replace_wort, STRING_MAX, stdin);
   chomp(replace_wort);
   i=0;
   while(1)
      {
         if( (zgr=strstr(&alt_string[i], such_wort)) == NULL)
            { /*Kein Wort zu ersetzen*/
               strcat(neu_string, &alt_string[i]);
               break;
            }
         else
            { /*..ansonsten von Byte i bis zgr in neu_string*/
               strncat(neu_string, &alt_string[i],
               zgr-&alt_string[i]);
               /*Jetzt überprüfen, ob wir ein Wort haben
                 und keinen Teilstring oder das Wort
                 am Anfang steht*/
               if( (zgr-&alt_string[0]==0 ||
                    wort_begrenzer( *(zgr-1))) &&
                    wort_begrenzer( *(zgr+such_wortlen)))
                 {
                    strcat(neu_string, replace_wort);
                    /*Nach ersetztem Wort den Zeiger setzen...*/
                    i+=zgr+such_wortlen-&alt_string[i];
                 }
               else
                  {
                     strncat(neu_string, zgr, 1);
                     i+=zgr+1-&alt_string[i];
                  }
            }
      }/*Ende while(1)*/
   /* Für Testausgabe ...*/
   /* printf("Neuer String : %s\n",neu_string); */
   strcpy(alt_string, neu_string);
   /*Achtung jetzt wirds ernst,
     für Testausgabe in Kommentar setzen*/
   fputs(alt_string,copy);
   neu_string[0] = '\0';
   return 0;
}

19.17. Blockweise Lesen und Schreiben - fread und fwrite            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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 besitzen könnte. Dies 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:

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.

19.17.1 Blockweises Lesen - fread()
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 19.6: Blockweise Lesen mit fread()
Abbildung 19.6: Blockweise 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:

#include <stdio.h>

int main()
{
   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 0;
}

Folgende Zeile soll wie oben interpretiert werden:

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

fread() ließt 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 15, Zeiger, wurde dies bereits durchgenommen.

19.17.2 Blockweises Schreiben - fwrite()
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 19.7: Blockweise Schreiben mit fwrite()
Abbildung 19.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,"Bill");
strcpy(adressen.vornam,"Clinton");
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:

#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()
{
   FILE *save;
   save=fopen("adressen.dat","r+b");
   if( NULL == save)
      {
         save = fopen("adressen.dat","w+b");
         if( NULL == save )
            {
               fprintf(stderr,
                       "Kann \"adressen.dat\" nicht oeffnen!\n");
               exit (0);
            }
      }

   /*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");
         exit (1);
      }

   /*Wir geben unseren FILE-Zeiger wieder frei*/
   fclose(save);
}

void ausgabe()
{
   FILE *output;
   output=fopen("adressen.dat","r+b");
   if( NULL == output )
      {
         fprintf(stderr,
                      "Konnte \"adressen.dat\" nicht oeffnen\n");
         exit (2);
      }

   /*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()
{
   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()
{
   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 0;
}

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:

   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():

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():

19.17.3 Big-Endian und Little Endian
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:

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

Tabelle 19.7: Little-Endian und Big-Endian im Vergleich

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:

#include <stdio.h>
typedef unsigned int  WORD;
typedef unsigned char BYTE;

int main()
{
   /* 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 0;
}

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

Und einige Big-Endian-Systeme:

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.

19.18. Datei (Stream) erneut öffnen - freopen            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Die Syntax von freopen():

#include <stdio.h>

FILE *freopen(const char *pfad,const char *modus,FILE *datei);

Mit freopen() wird zuerst versucht, den Stream datei zu schließen. Fehler beim Schließen werden dabei ignoriert. Danach wird dem FILE-Zeiger die Datei, die in pfad angegeben wurde, zugeordnet. Als modus können dieselben Modi verwendet werden wie bei der Funktion fopen(). Hierzu ein Beispiel:

#include <stdio.h>
#include <stdlib.h>
#define MAX 80

int main()
{
   FILE *quelle;
   char datei1[20],datei2[20];
   char puffer[MAX];

   printf("Welche Datei wollen Sie als erste lesen : ");
   scanf("%19s",datei1);
   printf("Welche Datei wollen Sie anschließend lesen : ");
   scanf("%19s",datei2);
   quelle=fopen(datei1,"r");
   if( NULL == quelle )
      {
         fprintf(stderr,"Fehler beim öffnen von %s\n",datei1);
         exit(0);
      }
   while(fgets(puffer,MAX,quelle) != NULL)
      fputs(puffer,stdout);
   printf("\n");
   freopen(datei2,"r",quelle);
   while(fgets(puffer,MAX,quelle) != NULL)
      fputs(puffer,stdout);
   return 0;
}

Mit der Zeile

freopen(datei2,"r",quelle);

wurde ein Schließen der Datei mit fclose() und ein Öffnen der Datei datei2 gespart. Da über freopen() die Datei mit dem FILE-Zeiger (datei1) automatisch geschlossen wurde, wird dem FILE-Zeiger quelle gleich die Datei mit dem Pfad datei2 übergeben.

So wird freopen() aber nicht unbedingt angewandt. Da freopen() nicht überprüft, ob der Stream ordnungsgemäß geschlossen wurde, rate ich auch davon ab. Ein Fehler wird schlicht ignoriert.

freopen() wird hauptsächlich dazu benutzt, die Standard-Streams stdin, stdout und stderr zu verbinden. In einfachen Worten: die Streams der Ein-/Ausgabe umzuleiten. Hierzu ein einfaches Beispiel:

#include <stdio.h>

int main()
{
   printf("Wir schreiben eine Datei \"log.txt\"\n");
   freopen("log.txt","a+",stdout);
   printf("Dieser Text steht in der Datei \"log.txt\"\n");
   return 0;
}

Hier wird z.B. die Standardausgabe in die Datei "log.txt" umgeleitet. Das erste printf() wird noch auf dem Bildschirm ausgegeben. Das zweite wird dagegen in die Datei "log.txt" umgeleitet, die sich hier im selben Verzeichnis wie das Programm befinden sollte.

Weiter mit 19.19. Datei löschen oder umbenennen - remove und rename            zum Inhaltsverzeichnis