ein Kapitel zurück                                           ein Kapitel weiter

Nun eigentlich ist dieses Kapitel nichts anderes als ein weiteres Kapitel zu den Zeigern. Nur anstatt wie in den vorangegangenen Kapiteln mit statischen Speicherbereichen zu hantieren wollen wir das ganze jetzt dynamisch machen. Dazu benötigen sie erst mal ein kurze Erklärung was ein Heap ist....




Ein Heap ist ein großer zusammenhängender Speicherbereich, der einem C - Programm zur Laufzeit als Speicherlieferant dient.




Diese Funktion mit der es Möglich ist während der Laufzeit unseres Programms für Variablen Speicherplatz zu reservieren lautet...

void *malloc(size_t size);

Bei Aufruf dieser Funktion wird uns auf dem Heap ein Hauptspeicherbereich der Größe size Bytes und liefert einen Zeiger auf das erste Byte des Speicherblockes. Hier ein Programmbeispiel....

/*Download:dyn1.c*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p;

p=(int *)malloc(sizeof(int));

if(p != NULL)
*p=99;

else
fprintf(stderr,"Kein Speicherplatz vorhanden!!!\n");

return 0;
}

Erst haben wir eine int-Zeiger definiert. Anschließend haben wir für unsern Zeiger mit...

p=(int *)malloc(sizeof(int));

...einen Speicherbereich der Größe int zugewiesen. Bei Erfolg zeigt p auf das 1.Byte des reservierten Speichers. Bei Misserfolg zeigt unser Zeiger auf NULL und es wird uns eine entsprechende Fehlermeldung ausgegeben das kein Speicherplatz reserviert werden konnte....


malloc



NULL - Zeiger:
Der NULL - Zeiger wird zurückgeliefert wenn malloc nicht mehr genügend zusammenhängenden Speicher finden kann. Der NULL - Zeiger ist ein vordefinierter Zeiger dessen Wert sich vom regulären Zeigern unterscheidet. Man verwendet den NULL - Zeiger vorwiegend bei Funktionen die eine Zeiger als Rückgabewert liefern als Anzeige eines Fehlers.

Wie im Programm gesehen steht die Funktion malloc in der Headerdatei <stdlib.h

Bei einem Aufruf von malloc muss die Anzahl der zu reservierenden Bytes angegeben werden. Damit ist die Größe der Objekts gemeint das durch den Zeiger referenziert werden soll. Sie können die Größe auch mit numerischen Werten selbst bestimmen wie z.B.....

p=(int *)malloc(sizeof(2));

Hier also 2 Bytes für eine int - Zeiger. Aber was machen sie wenn sie das Programm auf ein anderes System portieren wollen? Daher ist es oft besser die Größe des Zielobjektes vom Compilers selbst bestimmen zu lassen wie wir es oben im Programm gemacht haben. Sie haben 3 Möglichkeiten der dynamischen Speicherzuweisung...

1.Als Numerische Konstante:


p=(int *)malloc(sizeof(2));

Nachteil habe ich eben erwähnt.

2. Die Angabe des Types von sizeof:


p=(int *)malloc(sizeof(int));

Diese Möglichkeit ist hat nur einen Nachteil. Was wenn sie anstatt int auf einmal double - Werte wollen. Sie müssen mühsam alle Speicherzuweisungen umändern in...

p=(double *)malloc(sizeof(double));

...oder aber sie haben die Möglichkeit...

3. Den derferenzierten Zeiger selber für den sizeof - Operator zu verwenden.


p=(double *)malloc(sizeof(*p));

Aber Achtung wehe dem sie Vergessen den Dereferenzierungsoperator '*' wie im folgendem Programm....

/*Download:dyn2.c*/
#include <stdio.h>
#include <stdlib.h>


int main()
{
double *p1,*p2;

p1=(double *)malloc(sizeof(p1));
p2=(double *)malloc(sizeof(p2));

if(p1 != NULL && p2 != NULL)
{
*p1=5.15;
printf("p1 = %f\n",*p1);

*p2=10.99;
printf("p2 = %f\n",*p2);
printf("p1 = %f\n",*p1);
}
return 0;
}

Wenn sie "Glück" haben stürzt ihnen das Programm ab. Wenn sie Pech haben funktioniert das Programm und gibt sogar die richtigen Zahlen aus. Aber das wäre dann purer Zufall. Denn ohne die Dereferenzierungsoperatoren wird nicht ein double - Wert an malloc übergeben, sondern die Größe des Zeigers. Und der ist üblicherweise 4 Bytes anstatt der Erforderlichen 8 Bytes groß. Wenn jetzt zufällig ein anderer Wert an dieser Adresse in dem Heap hinkommt, ist die vorhersage für den Verlauf des Programms nicht absehbar. Man spricht von einer sogenannten Überlappung der Speicherbereiche.

Nun wenn wir Speicher vom Heap holen sollten wir ihn auch wieder zurückgeben können. Denn allozierten Speicher geben wir mit....

void free (void *p)

...wieder zurück. Der Speicher wird übrigens auch Freigegeben ohne free() wenn sie das Programm beenden. Daher dient free() auch eigentlich nur zur Ausführung während des Programmablaufes um wieder Speicher zurückzugeben. Kurz ein Programm zur Demonstration von free()....

/*Download:dyn3.c*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p;

p=(int *)malloc(sizeof(int));

if(p != NULL)
*p=99;

else
fprintf(stderr,"Kein Speicherplatz vorhanden!!!\n");

free(p); /*Wir geben den Speicher wieder zurück*/

return 0;
}

Aber auch hier muss ich sie warnen. Mittels free() wird zwar der zugewiesene Speicher als frei markiert, aber p zeigt immer noch auf die Ursprüngliche Speicherstelle. Hier ein Beispiel worauf ich hinaus will....

/*Download:dyn4.c*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p;

p=(int *)malloc(sizeof(int));

if(p != NULL)
*p=99;

else
fprintf(stderr,"Kein Speicherplatz vorhanden!!!\n");

printf("vor free() *p = %d\n",*p);

free(p); /*Wir geben den Speicher wieder zurück*/

printf("nach free() *p = %d\n",*p);

return 0;
}

Wenn sie wirklich sichergehen wollen das in unserem Beispiel der Pointer '*p' nichts mehr zurückgibt so müssen sie ihm nach 'free(p)' die Adresse von dem NULL-Zeiger übergeben....

free(p);
*p=NULL;


Dies könnte man aber auch schön in ein Makro verpacken....

#define free(x)  free(x);*x=NULL


Was malloc und die weiteren Speicherallokanten so besonders und wichtig macht, ist die Möglichkeit von jedem X-Beliebigen Datentypen Speicher anzufordern. Sind es nun einfache Datentypen wie Strings, Arrays oder komplexe Strukturen. Wichtig ist es natürlich auch zu Verstehen das wir zum Beipiel mittels......

/*Download:dyn5.c*/
#include <stdio.h>
#include <string.h>

int main()
{
char *string;
string=(char *)malloc(50*sizeof(char));
if(string == NULL)
{
fprintf(stderr, "Konnte keinen Speicher reservieren\n");
exit(0);
}

strcpy(string, "HALLO WELT dynamisch\n");
printf("%s",string);
return 0;
}

....durch die Speicherallocation.......

string=(char *)malloc(50*sizeof(char));

...mit dem Zeiger string auf die Anfangsadresse des reservierten Speicher zeigen. Nicht wie manche Irrtümlicherweise annehmen, string hätte nun die Größe von, in diesem Beispiel, 50 Bytes....


malloc



Hier das ganze nochmals Bildlich. Nun Anhand diesem Beispiels kann man aber auch Erkennen das wir Speicherplatz verschwenden. Auch wenn heutzutage die Speicher Überdimensioniert sind kann sich das auf Dauer schlecht auf die Performance auswirken. Folglich sollten wir nur soviel Speicher reservieren wie wir auch wirklich benötigen......

/*Download:dyn6.c*/
#include <stdio.h>
#include <string.h>

int main()
{
char *string;
string=(char *)malloc(sizeof("HALLO WELT dynamisch\n"));
if(string == NULL)
{
fprintf(stderr, "Konnte keinen Speicher reservieren\n");
exit(0);
}

strcpy(string, "HALLO WELT dynamisch\n");
printf("%s",string);
return 0;
}

Natürlich könnte man das ganze auch in ein Anweisung packen.....

/*Download:dyn7.c*/
#include <stdio.h>
#include <string.h>

int main()
{
char *string;
char hallo[] = "HALLO WELT dynamisch\n";

strcpy(string=(char *)malloc(sizeof(hallo)),hallo);
printf("%s",string);
return 0;
}

Nur begehen wir dabei einen der meistgemachten Fehler mit malloc. Wir überprüfen hier nicht den Rückgabewert von malloc. Es ist nicht immer gesagt das auch wirklich Speicherplatz reserviert wurde. Dann auch hier gilt: Alles was schief gehen KANN, wird auch mal schiefgehen!!! Also vergessen sie niemals, wenn sie Speicher allokieren, den Rückgabewert zu überprüfen, ob die Allokierung erfolgreich verlief.

So jetzt haben wir mal den Grundstock gelegt für Verkettete Listen oder auch dynamische Datenstruktur genannt. Was auch der Beitrag unserer nächsten Kapitel sein wird.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf