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.22. Fehlerausgabe mit strerror und perror            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Wenn bei einem Systemaufruf ein Fehler auftritt, bekommt die globale Variable errno einen entsprechenden Fehlerwert gesetzt. Mit den beiden Funktionen strerror() und perror() können Sie diese Systemfehlermeldung ausgeben lassen. Die Variable errno ist in der Headerdatei <errno.h> wie folgt deklariert:

extern int errno;

Ebenfalls in dieser Headerdatei <errno.h> sind die Konstanten (Fehlernummern) deklariert, die die Variable errno annehmen kann. Jede dieser Konstanten beginnt mit dem Buchstaben "E". Diese Fehlernummern sind allerdings, abgesehen von zwei Konstanten, System- und Compiler-abhängig. Folgende zwei Konstanten sind auf allen Systemen gleich:

Konstante Bedeutung
EDOM Unzulässiges Argument für eine mathematische Funktion
ERANGE Ergebnis außerhalb des darstellbaren Bereichs

Tabelle 19.9: Fehlerbehandlungs-Konstanten für mathematische Funktionen

Weitere - allerdings wie schon erwähnt System- und Compiler-abhängige - Konstanten und ihre Bedeutungen sind:

Fehlercode Bedeutung
EZERO Fehler 0
EINVFNC Ungültige Funktionsnummer
ENOFILE Datei nicht gefunden
ENOPATH Pfad nicht gefunden
ECONTR Speicherblöcke zerstört
EINVMEM Ungültige Speicherblockadresse
EINVENV Ungültiges Environment
EINVFMT Ungültiges Format
EINVACC Ungültiger Zugriffscode
EINVDAT Ungültige Daten
EINVDRV Ungültige Laufwerksangabe
ECURDIR Versuch, das aktuelle Verzeichnis zu löschen
ENOTSAM Nicht das gleiche Gerät
ENMFILE Keine weiteren Dateien mehr
ENOENT Datei oder Verzeichnis existiert nicht
EMFILE Zu viele geöffnete Dateien
EACCES Zugriff verweigert
EBADF Ungültiger Datei-Deskriptor
ENOMEM Zu wenig Speicher
ENODEV Gerät existiert nicht
EINVAL Ungültiges Argument
E2BIG Argumentliste ist zu lang
ENOEXEC Fehler beim Exec-Format
EXDEV Kreuzverbindung von Geräten
EFAULT Unbekannter Fehler
EEXIST Datei existiert bereits

Tabelle 19.10: Fehlerbehandlungs-Konstanten bei Systemaufrufen

Dies dürften jetzt nicht alle gewesen sein bzw. auf anderen Systemen wieder zu viele. Sie sollten in der Headerdatei <errno.h> oder unter Linux auf der man-Page "intro" nachsehen.

Die Variable errno wird bei Programmstart normalerweise auf 0 gesetzt, da es keine Fehlernummer mit dem Wert 0 gibt. Deshalb sollte errno jedes Mal, wenn eine Systemfunktion aufgerufen wird, wieder auf 0 gesetzt werden. Im ersten Beispiel wird die Funktion perror() verwendet. Zuerst die Syntax zu dieser Funktion:

#include <stdio.h>

void perror(const char *meldung);

Wenn für meldung kein NULL-Zeiger angegeben wurde, wird der String meldung mit anschließendem Doppelpunkt, gefolgt von einer zur errno gehörenden Fehlermeldung, ausgegeben (mit abschließendem '\n'). Rufen Sie hingegen diese Funktion mit dem NULL-Zeiger auf, wird nur eine zur errno gehörende Fehlermeldung ausgegeben. Geschrieben wird diese Fehlermeldung auf die Standardfehlerausgabe (stderr).

#include <stdio.h>

int main(void)
{
   FILE *fp;

   fp = fopen("keinedatei.dat", "r");
   if (NULL == fp)
      perror("Kann nicht aus Datei lesen ");
   return 0;
}

Das Programm versucht, die Datei "keinedatei.dat" zu öffnen. Falls diese nicht existiert, wird eine entsprechende Fehlermeldung ausgegeben:

Kann nicht aus Datei lesen : No such file or directory 

Der Funktion perror() kann auch ein NULL-Zeiger übergeben werden:

perror(NULL);

In diesem Fall würde nur das Folgende ausgegeben:

No such file or directory (ENOENT)

Das Gleiche soll jetzt auch mit der Funktion strerror() realisiert werden. Die Syntax der Funktion:

#include <string.h>

char *strerror(int error_nr);

Die Funktion liefert als Rückgabewert einen Zeiger auf einen String, welcher zur Systemfehlermeldung der Variable errno passt. Der Parameter error_nr beinhaltet in der Regel die Fehlervariable von errno.

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void)
{
   FILE *fp;

   fp = fopen("keinedatei.dat", "r");
   if (NULL==fp)
     fprintf(stderr,"%s\n",strerror(errno));
   return 0;
}

Das Programm läuft genauso ab wie das Programm zuvor mit perror().

Mit dem nun folgenden Programm wird eine Datei mit dem Namen "testfile" erstellt. Zuerst soll die Datei zum Lesen geöffnet werden. Anschließend wird überprüft

if(errno==ENOENT)

ob die Variable errno den Wert der Konstante ENOENT hat. Wenn ja, bedeutet dies, dass keine solche Datei existiert, und sie soll somit neu angelegt werden. Sollte es Probleme beim Anlegen dieser Datei geben, so wird dies mit dem nächsten perror()-Aufruf ausgegeben.

#include <stdio.h>
#include <errno.h>
#define NOERROR 0

int main()
{
   FILE *fp;

   fp = fopen("testfile", "r");

   if(errno==ENOENT)
      {
         errno=NOERROR;
         fp = fopen ("testfile", "w");
         if(NULL==fp)
            perror(NULL);
         else
            printf("Datei \"testfile\" angelegt\n");
      }
   fclose(fp);
   return 0;
}

19.23. Formatiert in einem String schreiben und foramtiert aus einem String lesen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Die Funktion sprintf() ist in der Headerdatei <stdio.h> deklariert und nicht - wie häufig irrtümlicherweise angenommen - in <string.h>. Diese Funktion arbeitet genauso wie printf(), nur dass statt der Ausgabe auf dem Bildschirm formatiert in einen String geschrieben wird. So lassen sich verschiedene Formatelemente formatiert in einen String schreiben. Die Syntax dazu lautet:

int sprintf(const char *target_string, const char *format, ...);  

Mit dem ersten Parameter wird die Adresse des Zielstrings angegeben. Das zweite und die weiteren Argumente haben dieselbe Bedeutung wie schon bei der Funktion printf().

Diese Funktion eignet sich besonders gut, wenn Sie Zahlen in einen String konvertieren wollen. Zudem schreibt sie am Ende das '\0'-Zeichen. Dazu ein kurzes Listing:

#include <stdio.h>
#define BUF 255

int main()
{
   char string1[BUF];
   char string2[BUF];
   int anzahl = 10;
   char gegenstand[] = "Kartoffel";
   float liter = 1.55f;
   char fluessigkeit[] = "Limo";

   sprintf(string1, "%d kg %s\n",anzahl,gegenstand);
   sprintf(string2, "%.2f Liter %s\n",liter,fluessigkeit);

   printf("%s",string1);
   printf("%s",string2);
   return 0;
}

Abbildung 19.9: Formatiert in einen String schreiben
Abbildung 19.9: Formatiert in einen String schreiben

Die Funktion sscanf() stellt das Gegenstück zur Funktion sprintf() dar. Sie funktioniert genauso wie scanf(), nur ließt sscanf() die Formatelemente nicht von der Tastatur ein, sondern von einem String. Die Syntax:

int sscanf(const char *quell_string, const char *format, ...); 

Im ersten Argument befindet sich der Quellstring, von dem anschließend die Eingabezeichen mit den Formatbezeichnern des zweiten Arguments in die Variablenliste eingelesen werden. Bei Dateiende oder Fehler gibt diese Funktion EOF zurück. Damit können Strings wieder in Zahlen umgewandelt werden. Hier ein Beispiel zu sscanf():

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
   int count=1;
   int check, temp, summe=0;
   if (argc < 2)
      {
         fprintf(stderr, "Verwendung: %s Integer"
                         " Integer [Integer_n]\n",*argv);
         exit(0);
      }
   while(count < argc)
      {
         /* Einzelne Argumente in Integerwerte konvertieren */
         check=sscanf(argv[count], "%d", &temp);
         /* Ungültiges Argument wird ignoriert */
         if(check == EOF)
            count++;
         else
            {
               summe+=temp;
               count++;
            }
      }
   printf("Summe aller Ganzzahlen der Argumente: %d\n", summe);
   return 0;
}

Mit diesem Listing werden die Ganzzahlen, welche dem Programm beim Aufruf über die Kommandozeile als Argumente mitgegeben wurden, als Summe berechnet. Die einzelnen Argumente werden mit der folgenden Zeile in Integerwerte konvertiert:

check=sscanf(argv[count], "%d", &temp); 

Zur Verdeutlichung noch ein weiteres Beispiel mit den beiden Funktionen sprintf() und sscanf():

#include <stdio.h>
#include <stdlib.h>
#define NUMITEMS 4

char *namen[4] = {"Jürgen", "Fatma", "Gismo", "Jonathan"};

int main(void)
{
   int schleife;
   char temp[4][80];
   char name[20];
   int alter;
   long lohn;

/*Wir erstellen Namen, Alter und Gehalt. Alter und Gehalt
  werden durch Zufallszahlen erzeugt. Mit sprintf schreiben
  wir die Daten formatiert int temp*/

   for (schleife=0; schleife < NUMITEMS; ++schleife)
      sprintf(temp[schleife], "%s %d %ld"
                    ,namen[schleife],rand()%20+18,rand()+27500L);

/*Wir erstellen eine Kopfzeile*/
   printf("%4s | %-20s | %5s | %9s\n"
           ,"#","Name","Alter","Gehalt");

   printf(" ----------------------------------------"
          "----------\n");

  /*Mit sscanf lesen wir die Daten formatiert aus temp aus,
    dort wo wir zuvor mit sprintf die Daten formatiert
    geschrieben haben*/

   for (schleife=0; schleife < NUMITEMS; ++schleife)
      {
         sscanf(temp[schleife],"%s %d %ld",&name,&alter,&lohn);
         printf("%4d | %-20s | %5d | %9ld\n"
                 ,schleife+1,name,alter,lohn);
      }
   return 0;
}

Abbildung 19.10: Formatiertes Schreiben in und Lesen aus einem String
Abbildung 19.10: Formatiertes Schreiben in und Lesen aus einem String

Dies ist ein recht praktisches Beispiel. Zuerst wird mit sprintf() formatiert in das zweidimensionale Array temp geschrieben und anschließend mit sscanf() wieder formatiert ausgelesen.

19.24. Fortgeschrittenes Thema            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Mittlerweile haben Sie ja schon einiges gelernt in diesem Buch. Aber irgendwie waren alle Themen doch sehr theoretisch. Es wurde vorwiegend gezeigt, wie Sie eine Funktion einsetzen können und was dabei beachtet werden muss. Daher soll in diesem Abschnitt einmal etwas Praktisches gemacht werden, womit einige C-typische Stärken aufgezeigt werden können. Es muss auch erwähnt werden, dass dieses Programmbeispiel Ihnen einiges an Wissen abverlangt. Im Listing werden aber keine Konstrukte von C verwendet, mit denen Sie bisher noch nichts zu tun hatten. Wenn Sie so wollen, stellt dieses Programm eine Art Zwischenprüfung Ihrer Kenntnisse in C dar.

Es soll gezeigt werden, wie Sie einen Text einer Datei dynamisch in den Speicher lesen können; wie dies bei Textverarbeitungsprogrammen geschieht. Eine einfache Textdatei ist dabei wie folgt strukturiert:

Abbildung 19.11: Einfache Strukturierung einer Textdatei
Abbildung 19.11: Einfache Strukturierung einer Textdatei

Sicher ist dies nur eine einfache Strukturierung einer Textdatei. In der Regel fehlen hierbei z.B. noch die Absätze oder die Seitenzahlen. Aber für unseren Fall genügt dies.

Folgende Funktionen benötigen Sie für diese Aufgabe:

Hier jetzt das vollständige Listing, welches sehr ausführlich kommentiert ist:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* symb. Konstanten */
#define LINE 255
#define ALLOC_LINE 10

enum { SUCCESS, ERROR };

/* Funktions-Prototypen */
int read_file(char ***, char *, int *);
char **alloc_string_array(int, int);
int zeilen_hinzu_fuegen(char ***, int, int, int);
int string_anpassen(char **, int);
int string_groesse_veraendern(char **, int, int);
void free_string_array(char **, int);

/*  ***array == Ein Zeiger auf einen Zeiger einer Zeile mit
 *  einem Zeiger auf die Länge der Zeile (*array[zeile][länge])
 *  *fname == Name der zu öffnenden Datei
 *  *zeile_n == Zeiger auf Anzahl Zeilen
 *  Rückgabewert: SUCCESS wenn kein Fehler, ansonsten ERROR
*/
int read_file(char ***array, char *fname, int *zeile_n)
{
 char puffer[LINE] = {0}; /*Puffer zum zeilenweisen Einlesen*/
 char *newline = NULL;    /*Zeiger für neue Zeile*/
 FILE *f;                 /*Datei, welche geöffnet werden soll*/
 int error = SUCCESS;     /*Fehlerstatus*/
 int zeile = 0;           /*aktuelle Zeile*/
 int absatz_n;

 *zeile_n = 0;            /*Erste Zeile mit 0 initialisieren*/
 /* Speicher anfordern für ALLOC_LINE Zeilen a LINE Zeichen */
 *array = alloc_string_array(ALLOC_LINE, LINE);
 if(NULL != *array)
   {
     f = fopen(fname, "r"); /* Datei fname zum Lesen öffnen */
     if(NULL != f)
       {
         *zeile_n = ALLOC_LINE;
         absatz_n = 0;
         /* Solange kein Fehler auftritt, zeilenweise einlesen */
         while(0 == error && NULL != fgets(puffer, LINE, f))
           {
             newline = strchr(puffer, '\n');
             if(NULL != newline)
     /*Newline-Zeichen gegen Terminierungszeichen austauschen*/
               *newline = '\0';
             strcat( (*array)[zeile], puffer);
             if(NULL != newline)
               {
                 absatz_n = 1;
                 zeile++;
    /* Haben wir noch Platz im Speicher für weitere Zeilen */
                 if(zeile >= *zeile_n)
                  { /* Nein, dann an anfügen */
                    if(0 == zeilen_hinzu_fuegen(array,
                                     *zeile_n, ALLOC_LINE, LINE))
                        error = ERROR;
                    else
                        /* Anzahl der Zeilen + 10 */
                        *zeile_n += ALLOC_LINE;
                  }
               }
   /* Kein Newline-Zeichen, dann Zeile länger als LINE Zeichen,
      String der Länge anpassen -> Speicher anfordern */
             else
               {
                 absatz_n++;
                 if(0 == string_groesse_veraendern(*array,
                                       zeile, absatz_n * LINE))
                     error = ERROR;
               }
           }/*while*/
         fclose(f);
/* Wir sind am Ende vom Einlesen oder ein Fehler trat auf.
   Es müssen allerdings noch die übrige(n) Zeile(n) in
   den Speicher eingelesen werden */
         if(0 == error && *zeile_n > zeile)
           {
             if(0 == zeilen_hinzu_fuegen(array,
                                    *zeile_n, zeile-*zeile_n, 0))
                 error = ERROR;
             *zeile_n = zeile;
           }
       }
     else  /* Datei fname konnte nicht geöffnet werden */
         error = ERROR;
   }
 else   /* Es konnte kein Speicher allokiert werden */
     error = ERROR;

 if(error != 0)
   { /* Im Fall eines Fehler, Speicher wieder freigeben
        und Anzahl Zeilen auf 0 */
     free_string_array(*array, *zeile_n);
     *zeile_n = 0;
   }
 else
     string_anpassen(*array, *zeile_n);
 return error;
}


/*   zeilen_n == Anzahl Zeilen, wieviele reserviert werden sollen
 *   laenge   == Speicherplatz für die Länge jeder Zeile,
 *               die angefordert wird
 *   Rückgabewert: Anfangsadresse des reservierten Speichers vom
 *                 String-Array  (array[zeile][laenge])
 */
char **alloc_string_array(int zeilen_n, int laenge)
{
 char **array = NULL;
 int zeile;
 int ret = SUCCESS;

 if(zeilen_n > 0 && laenge > 0)
   { /* Speicher für zeilen_n Zeilen reservieren */
     array = (char **)malloc(zeilen_n * sizeof(*array));
     if(NULL != array)
       {
         for(zeile=0; zeile < zeilen_n; zeile++)
           {/* Für jede zeile_n Zeile laenge Bytes
               Speicher reservieren */
             array[zeile] =(char *) malloc( laenge *
                                        sizeof(*array[zeile]) );
             if(NULL == array[zeile])
                ret = ERROR;
             else
             /*jede Zeile erstes Zeichen mit \0 initialisieren*/
                array[zeile][0] = '\0';
           }
         if(ERROR == ret)
           {/* Bei Fehler Speicher freigeben */
             free_string_array(array, zeilen_n);
             array = NULL;
           }
       }
    }
 return array;
}


/*  ***array    == Ein Zeiger auf einen Zeiger einer Zeile mit
 * einem Zeiger auf die Länge der Zeile ( *array[zeile][länge])
 * alt_n_zeilen   == Anzahl akt. Zeilen im Speicher
 * n_zeilen_hinzu == Anzahl Zeilen, für die neuer Speicherplatz
 * reserviert werden soll. Bei negativen Wert werden n Zeilen
 * entfernt
 * init_laenge    == Speicherplatz für die Länge jeder Zeile, die
 *                   angefordert wird
 * Rückgabewert: 1 wenn Ok, ansonsten 0
*/
int zeilen_hinzu_fuegen(char ***array_ptr, int alt_n_zeilen,
                        int n_zeilen_hinzu, int init_laenge)
{
 char **ptr;
 int ret = 1;
 int zeile;
 int anzahl_alte_zeilen = alt_n_zeilen;
 /* Ein negativer Wert bedeutet Zeilen entfernen */
 if(n_zeilen_hinzu < 0)
   {
     for(zeile=anzahl_alte_zeilen-1;
         zeile >=anzahl_alte_zeilen+n_zeilen_hinzu; zeile--)
        free( (*array_ptr)[zeile]);
   }
 /* Speicher für einzelne Zeilen reservieren */
 ptr=(char **)realloc( *array_ptr,
        (anzahl_alte_zeilen+n_zeilen_hinzu)*sizeof(**array_ptr));
 if(NULL != ptr)
   {
     *array_ptr = ptr;
     for(zeile=anzahl_alte_zeilen;
         ret && zeile < anzahl_alte_zeilen+n_zeilen_hinzu;
         zeile++)
       {/* Anzahl der Zeichen, welche jede Zeile
           aufnehmen kann, reservieren */
         (*array_ptr)[zeile] =(char *) malloc(init_laenge);
         if( NULL != (*array_ptr)[zeile])
         /* jede Zeile erstes Zeichen mit \0 initialisieren  */
             (*array_ptr)[zeile][0] = '\0';
         else
             ret = 0;
       }
   }
 else
     ret = 0;
 return ret;
}


/*  **array_ptr == Ein Zeiger auf das String-Array
 *                 array[zeile][laenge]
 *      zeile_n == Anzahl Zeilen, welche angepasst werden
 * Rückgabewert bei Erfolg 0, ansonsten größer als 0
 */
int string_anpassen(char **array_ptr, int zeile_n)
{
 int zeile;
 int anzahl_zeichen;
 int fehlschlag = 0;

 for(zeile = 0; zeile < zeile_n; zeile++)
   {/* Funktion strlen ließt das Terminierungszeichen
       '\0' nicht mit -> daher +1 */
     anzahl_zeichen = strlen(array_ptr[zeile])+1;
     if(0 == string_groesse_veraendern(array_ptr,
                                         zeile, anzahl_zeichen))
       fehlschlag++;
    }
 return fehlschlag;
}


/*  **array_ptr == Ein Zeiger (Adresse) auf das String-Array
 *  array[zeile][laenge]
 *      zeile   == Zeile, welche verändert werden soll
 * neu_laenge   == Anzahl Zeichen, die für die Zeile verändert
 *                 werden soll
 * Rückgabewert bei Erfolg SUCCESS, ansonsten bei Fehler ERROR
 */
int string_groesse_veraendern(char **array, int zeile,
                              int neu_laenge)
{
 char *ptr;
 int ret = SUCCESS;

 ptr =(char *) realloc(array[zeile], neu_laenge);
 if(ptr != NULL)
   array[zeile] = ptr;
 else
   ret = ERROR;

 return ret;
}


/*  **array_ptr == Ein Zeiger (Adresse) auf das String-Array
                   array[zeile][laenge]
 *    n_zeile   == Anzahl Zeilen, die freigegeben werden sollen*/
void free_string_array(char **array, int n_zeilen)
{
 int zeile;
 if(array != NULL)
   {
     for(zeile= 0; zeile < n_zeilen; zeile++)
       {
         if(array[zeile] != NULL)
             free(array[zeile]);
       }

   }
 free(array);
}

int main()
{
 char **array = NULL;
 char datei[255];
 int zeilen=0, i, auswahl, n, m;

 do{
     printf("Was wollen Sie tun?\n\n");
     printf("-1- Datei komplett in den Speicher einlesen\n");
     printf("-2- Inhalt der Datei im Speicher ausgeben\n");
     printf("-3- Datei im Speicher v. Zeile n bis m ausgeben\n");
     printf("-4- Den Speicher wieder freigeben\n");
     printf("-5- Ende\n\n");
     printf("Ihre Wahl : < >\b\b");
     scanf("%d",&auswahl);fflush(stdin);
     switch(auswahl)
       {
         case 1 : printf("Datei angeben( mit Pfadangabe ): ");
                  scanf("%254s",datei);
                  fflush(stdin);
                  if( (read_file(&array, datei, &zeilen))==ERROR)
                    printf("Fehler beim Lesen in Speicher!!!\n");
                  break;
         case 2 : if(zeilen == 0)
                      printf("Keine Daten vorhanden!\n");
                  else
                      for(i=0; i<=zeilen-1; i++)
                          printf("%s\n",array[i]);
                  break;
         case 3 : printf("Zeilenbeginn: ");
                  scanf("%d",&n);
                  fflush(stdin);
                  if(n > 0 && n <= zeilen-1)
                    {
                      printf("bis zur Zeile: ");
                      scanf("%d",&m);
                      fflush(stdin);
                      if(m >= n && m <= zeilen-1)
                         {
                            for(i=n; i<=m-1; i++)
                                printf("%s\n",array[i]);
                         }
                      else
                         printf("??>>%d<<??\n",m);
                    }
                  else
                     printf("??>>%d<<??\n",n);
                  break;
         case 4 : free_string_array(array, zeilen);
                  zeilen=0;
                  break;
         default: break;
       }
   }while(auswahl != 5);
 return 0;
}

Weiter mit 19.25. Low-Level-Datei-I/O Funktionen (nicht ANSI-C)            zum Inhaltsverzeichnis