12. Dynamische Speicherverwaltung
  Bisher wurden nur Datentypen behandelt, deren Speicherplatzbedarf bereits zur
  Compilierungszeit feststanden und beim Erzeugen des Programms eingeplant werden konnten. Es
  ist jedoch nicht immer möglich, den Speicherbedarf exakt vorher zu planen, und es ist
  unökonomisch, jedesmal sicherheitshalber den maximalen Speicherplatz zu reservieren.
  C bietet daher die Möglichkeit, Speicherbereiche während des Programmlaufs zu
  reservieren, d.h. dem Programm zur Verfügung zu stellen. Wichtig ist, dass die
  reservierten Speicherbereiche spätestens zum Programmende wieder freigegeben
  werden.
  
12.1. Speicherbereiche reservieren
  Speicherbereiche reservieren heißt, zur Laufzeit des Programms einen
  zusammenhängenden Speicherbereich dem Programm zugänglich zu machen und nach
  außen hin diesen Speicherbereich als belegt zu markieren. Dieser Speicherbereich
  liegt im sogenannten Heap (zu deutsch Halde), einem
  großen Speicherbereich, der vom Betriebssystem verwaltet wird.
  Zum Reservieren von Speicherbereichen wird eine der beiden Funktionen
  malloc (steht für Memory Allocation) oder
  calloc (steht für Cleared Memory Allocation) verwendet. Dazu
  muss noch die Header-datei stdlib.h oder
  malloc.h eingebunden werden. Die Funktionen sind wie folgt
  deklariert:
  
  void *malloc(int Groesse);
  void *calloc(int Anzahl_Elemente, int Groesse_eines_Elements);
  Beide Funktionen liefern einen void-Zeiger auf den reservierten
  Speicherbereich (bei calloc wird der Speicher mit 0 initialisiert, bei
  malloc sind die Werte des Speicherbereichs undefiniert) oder den
  NULL-Zeiger, wenn nicht mehr genügend Speicher vorhanden ist. Wird
  beim Reservieren des Speichers ein Zeiger auf einen anderen Datentypen benötigt, wird
  der Ergebniszeiger von malloc bzw. calloc
  entsprechend implizit umgewandelt (siehe auch Typumwandlung). Achtung: In
  C++ (d.h. auch bei Verwendung eines C++-Compilers für die Programmierung in C) muss
  er explizit umgewandelt werden!
  Beispiel:
   kap12_01.c
  kap12_01.c
  
  01 #include <stdio.h>
  02 #include <stdlib.h>
  03 
  04 int main()
  05 {
  06    double *t = malloc(2 * sizeof(*t));
  07 
  08    if (t != NULL)
  09    {
  10       *t = 3.1415296;
  11       *(t + 1) = 2 * *t;
  12       printf("    PI = %f\n", *t);
  13       printf("2 * PI = %f\n", *(t + 1));
  14 
  15       free(t);   /* Speicher wieder freigeben */
  16    }
  17    else
  18       printf("Kein Speicher verfuegbar!\n");
  19 
  20    return 0;
  21 }
  
  Zum Zeitpunkt der Compilierung wird nur der Platz für den Zeiger t
  eingeplant. Mit der Initialisierung des Zeigers wird Speicherplatz in der Größe
  von 2 * sizeof(*t) Bytes (also 2 * sizeof(double)
  Bytes) zur Laufzeit des Programms bereitgestellt.
  Der Zeiger t zeigt anschließend auf diesen Speicherplatz (sofern
  Speicher vorhanden ist!).
  Da Arrays und Zeiger intern identisch dargestellt und verarbeitet werden, lässt
  sich das oben angegebene Beispielprogramm auch mit Arrays schreiben.
  Beispiel:
   kap12_02.c
  kap12_02.c
  
  01 #include <stdio.h>
  02 #include <stdlib.h>
  03 
  04 int main()
  05 {
  06    double *t = malloc(2 * sizeof(*t));
  07 
  08    if (t != NULL)
  09    {
  10       t[0] = 3.1415296;
  11       t[1] = 2 * t[0];
  12       printf("    PI = %f\n", t[0]);
  13       printf("2 * PI = %f\n", t[1]);
  14 
  15       free(t);   /* Speicher wieder freigeben */
  16    }
  17    else
  18       printf("Kein Speicher verfuegbar!\n");
  19 
  20    return 0;
  21 }
  
  Es lassen sich auch Speicherbereiche für Strukturen reservieren. Im folgenden Beispiel
  wird eine Struktur für imaginäre Zahlen definiert. Dann wird mit Hilfe der
  calloc-Funktion ein Speicherbereich für 2 Strukturen reserviert
  und dieser Bereich mit Werten gefüllt.
  Beispiel:
   kap12_03.c
  kap12_03.c
  
  01 #include <stdio.h>
  02 #include <stdlib.h>
  03 
  04 int main()
  05 {
  06    struct Imag
  07    {
  08       double Re;
  09       double Im;
  10    };
  11    struct Imag *I = calloc(2, sizeof(*I));
  12 
  13    if (I != NULL)
  14    {
  15       I->Re = 1;
  16       I->Im = 0;
  17       (I + 1)->Re = 0;
  18       (I + 1)->Im = 1;
  19       printf("  *I   = %f / %f\n", I->Re, I->Im);
  20       printf("*(I+1) = %f / %f\n", (I + 1)->Re, (I + 1)->Im);
  21 
  22       free(t);   /* Speicher wieder freigeben */
  23    }
  24    else
  25       printf("Kein Speicher verfuegbar!\n");
  26 
  27    return 0;
  28 }
  
  Die Größe des mit malloc bzw. calloc reservierten
  Speicherbereiches lässt sich mit der Funktion realloc vergrößern
  oder verkleinern. Dazu muss der Funktion ein Zeiger auf den bisherigen Speicherbereich und die neue
  Größe in Bytes angegeben werden. Je nach Compiler wird intern der reservierte Speicherbereich
  erweitert bzw. reduziert (sofern hinter dem bisher reservierten Speicherbereich noch genügend
  freier Heap vorhanden ist) oder es wird ein neuer Speicherbereich in der gewünschten Größe
  reserviert und der alte Speicherbereich anschließend freigegeben. Dabei bleiben die Daten vom alten
  Speicherbereich erhalten, d.h. ist der neue Speicherbereich größer, werden die Daten komplett
  in den neuen Speicherbereich kopiert, der restliche Speicherbereich wird nicht initialisiert. Ist dagegen
  der neue Speicherbereich kleiner, werden die Daten nur bis zur Größe des neuen Speicherbereichs
  kopiert, der Rest geht verloren.
  Die Funktion ist folgendermaßen deklariert:
  void *realloc(void *AlterSpeicherbereich, int NeueGroesse);
  Beispiel:
   kap12_04.c
  kap12_04.c
  
  01 #include <stdio.h>
  02 #include <stdlib.h>
  03 
  04 int main()
  05 {
  06    int *pArray = malloc(5 * sizeof(int));
  07    int i;
  08 
  09    if (pArray)
  10    {
  11       for (i = 0; i < 5; i++)
  12       {
  13          *(pArray + i) = i + 1;
  14          printf("%i\n", *(pArray + i));
  15       }
  16 
  17       pArray = realloc(pArray, 1000 * sizeof(int));
  18 
  19       if (pArray)
  20       {
  21          for (i = 0; i < 1000; i++)
  22          {
  23             *(pArray + i) = i + 1;
  24             printf("%i\n", *(pArray + i));
  25          }
  26 
  27          free(pArray);
  28       }
  29    }
  30 
  31    return 0;
  32 }
  
  Bei diesem Beispielprogramm wird erst Speicher für 5 Integerwerte reserviert, die anschließend
  in einer Schleife gesetzt und auf dem Bildschirm ausgegeben werden. Dann wird in Zeile 17 der reservierte
  Speicherbereich erweitert auf 1000 Integerwerte, die genauso in einer Schleife gesetzt und auf dem
  Bildschirm ausgegeben werden. Wird die Zeile 17 auskommentiert, kommt es zur Laufzeit zu einem
  Speicherzugriffsfehler.
  Besonderheiten der realloc-Funktion: Wird als neue Größe eine 0 angegeben,
  wird der alte Speicherbereich freigegeben; die Funktion realloc entspricht dann der
  Funktion free (siehe nächster Abschnitt). Wird als alter Speicherbereich der
  NULL-Zeiger angegeben, wird nur Speicher in der angegebenen Größe
  reserviert, d.h. die realloc-Funkion entspricht der
  malloc-Funktion.
  
12.2. Reservierte Speicherbereiche freigeben
  Die free-Funktion (benötigt ebenfalls die Headerdatei
  stdlib.h oder malloc.h) gibt den reservierten
  Speicherbereich wieder frei, damit dieser von neuem belegt oder anderen Programmen zur
  Verfügung gestellt werden kann. Dazu wird der Zeiger, der auf den freizugebenden
  Speicherbereich zeigt, der Funktion als Parameter angegeben. Im folgenden Beispiel wird ein
  Speicherbereich reserviert und gleich wieder freigegeben.
  Beispiel:
   kap12_05.c
  kap12_05.c
  
  01 #include <stdio.h>
  02 #include <stdlib.h>
  03 
  04 int main()
  05 {
  06    void *z = malloc(1000); /* 1000 Bytes Speicher reservieren */
  07 
  08    if (z != NULL)
  09    {
  10       printf("Speicher reserviert!\n");
  11       free(z);             /* Speicher wieder freigeben       */
  12    }
  13    else
  14       printf("Kein Speicher verfuegbar!\n");
  15 
  16    return 0;
  17 }
  
  Nach dem Freigeben eines reservierten Speicherbereichs kann auf diesen nicht mehr
  zugegriffen werden!
  
12.3. Hinweise für die Verwendung von malloc, calloc und free
  Einige Dinge sollten bei der dynamischen Speicherverwaltung beachtet werden, deren
  Missachtung oder Unkenntnis in manchen Fällen ein unvorhersehbares
  Programmverhalten bzw. einen Systemabsturz nach sich zieht, leider
  ohne Fehlermeldung vom Compiler oder vom Betriebssystem.
   Die free-Funktion darf ausschließlich
  Speicherbereiche freigeben, die zuvor mit malloc, calloc
  oder realloc reserviert wurden!
  
     int i, *ip1,*ip2;
     ip1 = malloc(sizeof(*ip1));
     ip2 = &i;
     free(ip1);  /* OK! */
     free(ip2);  /* Fehler!!! */
   Die free-Funktion darf jeden reservierten Speicherbereich nur
  einmal freigeben. Falls zwei oder mehr Zeiger auf den gleichen reservierten Speicherbereich
  zeigen, darf free nur mit einen dieser Zeiger aufgerufen werden. Die
  anderen Zeiger verweisen danach wohl noch auf den ehemals reservierten Speicherbereich,
  dürfen aber nicht darauf zugreifen. Solche Zeiger werden dann
  hängende Zeiger (im englischen dangling pointer)
  genannt.
   free(NULL); bewirkt nichts; verursacht auch keinen
  Fehler.
   Reservierte Speicherbereiche unterliegen nicht den Gültigkeitsregeln von
  Variablen. D.h. sie existieren unabhängig von Blockgrenzen solange, bis sie wieder
  freigegeben werden oder das Programm beendet wird.
   Reservierte Speicherbereiche, auf die kein Zeiger mehr zeigt, sind nicht mehr
  zugänglich und werden verwitwete Bereiche genannt. Die englische
  Bezeichnung trifft das daraus resultierende Problem besser: memory
  leak (Speicherleck). Werden regelmäßig neue Speicherbereiche
  reserviert, ohne sie wieder freizugeben, bricht das Programm irgendwann wegen
  Speicherknappheit ab. Daher sollte gut darauf geachtet werden, nicht mehr benötigte
  Speicherbereiche wieder freizugeben.
  Ein weiterer Grund für das Abstürzen von Programmen, die über lange Zeit
  laufen, liegt in der Zerstückelung des Heap durch ständiges Reservieren und
  Freigeben von Speicherbereichen. Mit Zerstückelung ist gemeint, dass sich
  kleinere belegte und freie Bereiche abwechseln, so dass das Reservieren eines
  größeren zusammenhängenden Speicherbereichs nicht mehr erfüllt werden
  kann, obwohl die Summe aller einzelnen freien Plätze ausreichen würde.
  Dieses Problem verlangt ein Zusammenschieben aller belegten Plätze, so dass ein
  großer freier Bereich entsteht. Dies wird garbage collection
  (zu deutsch Müllsammlung) genannt. Die meisten C/C++-Compiler haben in ihrer
  Speicherverwaltung aus Effizienzgründen keine garbage collection eingebaut, weil sie
  nur in wenigen Fällen nötig ist und viel Rechenzeit benötigt.
  
  
  Voriges Kapitel: 11. Datei-Ein- und Ausgabe in C
  

