Programmieren in C mit dem z88dk

Software-Entwicklung, Compiler, Interpreter, ...
Benutzeravatar
bbock
Beiträge: 242
Registriert: 08.02.2015, 15:31

19. Dynamische Speicherverwaltung

Beitrag von bbock »

Werden in C Variablen deklariert, dann werden sie auf unterschiedliche Weise im Speicher angelegt. Im Falle von globalen Variablen werden sie zum Zeitpunkt der Kompilierung mit festen Speicheradressen versehen. Für lokale Variablen von Funktionen oder auch deren Parameter wird zur Laufzeit Speicher auf dem Stack reserviert, der beim Beenden der jeweiligen Funktion wieder freigegeben wird.

Allen ist gemeinsam, dass die Größe des zur Verfügung gestellten Speicherplatzes von vornherein festgelegt ist. Oft benötigt man aber Speicherplatz, dessen Größe nicht bereits zur Compile-Zeit bekannt ist, weil er z.B. von Benutzereingaben abhängt. Für diese Fälle braucht man eine dynamische Speicherzuweisung (engl.: dynamic memory allocation).

C bietet dafür Funktionen in der Standardbibliothek, die man mit #include <stdlib.h> in das eigene Programm einbindet. Dort finden wir u.a. die beiden wichtigsten Funktionen malloc() und free(). malloc() hat einen Parameter: die Größe des zu reservierenden Speichers. Die Funktion liefert einen void-Pointer zurück, der auf den Anfang des reservierten Speichers zeigt. Ein void-Pointer ist im Grunde eine Speicheradresse. Von welchem Typ die Daten im reservierten Speicher sein sollen, davon weiß malloc() nichts. Das Ergebnis wird einfach per type cast in den gewünschten Datentyp umgewandelt; dazu schreibt man den Datentyp einfach in Klammern vor den Funktionsaufruf:

Code: Alles auswählen

char *characters = (char *) malloc(200);
Hier wird Platz für 200 Zeichen reserviert; der Pointer characters zeigt anschließend auf das erste der 200 Zeichen. Sollte nicht mehr genügend Speicher verfügbar sein, dann liefert malloc() NULL zurück. Man sollte das Ergebnis immer auf NULL prüfen, sonst sind Abstürze vorprogrammiert.

Sobald der Speicher nicht mehr benötigt wird, muss er mit der Funktion free() wieder freigegeben werden. Tut man das nicht, dann enstehen sog. Speicherlecks, also Speicher, der dauerhaft reserviert bleibt und nicht mehr für andere sinnvolle Aufgaben verwendet werden kann. free() ist komplementär zu malloc(), d.h. zu jedem malloc()-Aufruf muss es einen korrespondierenden free()-Aufruf geben. Zum Freigeben des zuvor angeforderten Speichers muss man free() den Pointer übergeben, den malloc() zurückgeliefert hat. Man muss des Ergebnis des malloc()-Aufrufs also immer in einer Pointer-Variablen speichern, damit man später free() aufrufen kann.

Im folgenden kleinen Beispiel wollen wir eine Zeichenkette mit einer Anzahl Sternen aufbauen, die der Benutzer vorgibt. Wir speichern die Datei als testmalloc.c.

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
  int i, n, result;
  char *buffer;
 
  printf("Wieviele Sterne? ");
  scanf("%d", &i);
 
  buffer = (char*) malloc(i + 1);  // ein Byte extra für das String-Ende-Zeichen
  if (buffer != NULL) {            // sonst haben wir keinen Speicher bekommen
    buffer[i] = '\0';              // String-Ende-Zeichen setzen

    while(i--) {
      buffer[i] = '*';             // String mit Sternchen füllen
    }

    printf("%s\n", buffer);
    free(buffer);                  // Wichtig! Speicher wieder freigeben

    result = EXIT_SUCCESS;
  }
  else {
    result = EXIT_FAILURE;
  }
  
  return result;
}
Wir kompilieren mit

Code: Alles auswählen

zcc +cpm -subtype=pcw80 -compiler=sccz80 -DAMALLOC -create-app testmalloc.c -o malloc.com
Die Präprozessor-Option -DAMALLOC macht die Verwendung von malloc besonders einfach; sie stellt den automatischen Modus der Speicherverwaltung ein.

Wird die ausführbare Datei mit malloc gestartet, dann fragt das Programm nach der gewünschten Anzahl von Sternen. Gibt man z.B. 15 ein, dann wird Speicher für 16 Bytes reserviert, ein String mit 15 Sternen aufgebaut und anschließend per printf() ausgegeben.

Hier binden wir die Header-Datei stdlib.h ein, die ihrerseits malloc.h inkludiert. In diesem Beispiel hätte es genügt, nur malloc.h einzubinden, allerdings hätten dann die Konstanten EXIT_SUCCESS und EXIT_FAILURE nicht zur Verfügung gestanden.

Im Zusammenhang mit malloc() wird oft der Operator sizeof verwendet. Er dient dazu, den Speicherbedarf eines Datentyps zur Compile-Zeit zu bestimmen. Das funktioniert mit jedem in C deklarierten Datentyp, also auch mit komplexen Datentypen wie structs. Möchte man Speicher für zehn float-Variablen reservieren, dann geht das folgendermaßen:

Code: Alles auswählen

float *floatVars = (float*) malloc(20 * sizeof(float));
Der Compiler bestimmt also die Größe einer float-Variablen und durch Multiplikation mit 20 bekommen wir den Speicherplatz für 20 float-Variablen. Wie viel Speicher das tatsächlich ist, hängt vom System ab, auf dem das Programm kompiliert wird.

Für den Zugriff auf die einzelnen float-Variablen kann man auch die Array-Schreibweise verwenden. Möchte man die dritte float-Variable, also die mit dem Index 2, auf den Wert 5.3 setzen, dann geht das so:

Code: Alles auswählen

floatVars[2] = 5.5;
In der Standardbibliothek findet man weitere Funktionen zur dynamischen Speicherverwaltung. Darunter calloc(), bei der man die Anzahl der gewünschten Elemente und die Elementgröße als Parameter übergibt, und realloc() zur Änderung der Größe eines zuvor allozierten Speicherblocks.
Antworten