Menu

malloc in C: dynamische Speicherverwaltung, free und sizeof

Warum es den Heap gibt, wie malloc dir Speicher gibt, dessen Größe zur Laufzeit feststeht, das Idiom p = malloc(n * sizeof *p), die Prüfung auf NULL, genau einmal freigeben, und ein dynamisches Array von Anfang bis Ende.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Jedes Array, das du bisher geschrieben hast, hatte seine Größe zur Übersetzungszeit eingebacken: int scores[10]; reserviert zehn Plätze, ob du drei oder dreihundert brauchst. Das funktioniert, bis die Größe erst während des Programmlaufs bekannt ist - wie viele Zeilen eine Datei hat, wie viele Datensätze der Benutzer eingibt. Dynamischer Speicher löst das: Du forderst die Bytes an, die du brauchst, wenn du sie brauchst, und gibst sie zurück, wenn du fertig bist.

Die Werkzeuge stehen in <stdlib.h>:

#include <stdlib.h>

Warum es den Heap gibt

C gibt deinem Programm zwei Hauptbereiche des Speichers. Lokale Variablen leben im automatischen Speicher (dem Stack): Der Compiler bestimmt ihre Größe, und sie verschwinden in dem Moment, in dem ihre Funktion zurückkehrt. Dynamische Belegungen leben im Heap: Du bestimmst die Größe zur Laufzeit, und der Block bleibt am Leben, bis du ihn ausdrücklich freigibst.

Diese zweite Eigenschaft ist der eigentliche Grund, malloc zu benutzen. Eine Funktion kann einen Block belegen, den Zeiger zurückgeben, und der Speicher ist im Aufrufer noch gültig - etwas, das ein lokales Array nie kann. Die beiden Bereiche werden ausführlich unter Stack gegen Heap verglichen.

malloc: Bytes anfordern

malloc nimmt eine Byteanzahl entgegen und liefert ein void *, das auf so viele Bytes uninitialisierten Speichers zeigt, oder NULL, wenn die Anfrage scheitert.

Vier Dinge in diesem kurzen Programm sind die ganze Disziplin:

  1. Der Größenausdruck ist n * sizeof *scores. Lies ihn als „n von dem, worauf scores zeigt".
  2. Das Ergebnis wird gegen NULL geprüft, bevor es benutzt wird.
  3. Der Block wird genau wie ein Array benutzt - scores[i] funktioniert, weil Indizierung Zeigerarithmetik ist.
  4. Er wird einmal freigegeben, und der Zeiger wird danach auf NULL gesetzt.

Beachte, dass beim Rückgabewert kein Cast steht. In C wandelt sich void * automatisch in jeden Objektzeiger um, und (int *)malloc(...) zu schreiben fügt Rauschen hinzu und verdeckt ein fehlendes <stdlib.h>-Include. (C++ verlangt den Cast; C nicht.)

Das sizeof-Idiom

Warum sizeof *scores statt sizeof(int)? Weil es nicht veralten kann.

int  *a = malloc(n * sizeof *a);     /* n ints, was auch immer int hier ist */
long *b = malloc(n * sizeof *b);     /* dieselbe Zeilenform, richtige Größe */

/* die brüchige Form */
long *c = malloc(n * sizeof(int));   /* kompiliert, belegt zu wenig */

Die letzte Zeile ist ein echter Bug, vor dem kein Compiler warnt: c ist ein long *, die Belegung ist für int bemessen, und jeder Schreibzugriff hinter der ersten Hälfte läuft über das Ende hinaus. Mit sizeof *c folgt die Größe automatisch der Deklaration.

sizeof *p dereferenziert p nicht - sizeof wird zur Übersetzungszeit allein aus dem Typ ausgewertet und ist daher auch sicher, wenn p uninitialisiert oder NULL ist.

Uninitialisierter Speicher

malloc leert nicht, was es dir gibt. Die Bytes enthalten, was vorher dort lag.

Wenn du den Block genullt haben willst, erledigt calloc das in einem Schritt - siehe calloc und realloc.

free: zurückgeben

free(p) gibt den Block an den Allokator zurück. Drei Regeln:

  • Übergib genau den Zeiger, den malloc geliefert hat, nicht einen weitergerückten. free(p + 1) ist undefiniertes Verhalten.
  • Gib jeden Block genau einmal frei. Zweimal ist ein doppeltes Freigeben, das die Buchführung des Allokators beschädigt.
  • Nach dem Freigeben ist der Zeiger veraltet. Ihn zu benutzen ist ein Use-after-free.
int *p = malloc(sizeof *p);
free(p);
*p = 5;        /* Use-after-free - undefiniertes Verhalten */
free(p);       /* doppeltes Freigeben - undefiniertes Verhalten */

Keine der Zeilen muss sofort abstürzen, und genau das macht sie gefährlich: Das Programm kann minutenlang laufen und dann an einer völlig anderen Stelle scheitern. Die billige Verteidigung ist, den Zeiger zu leeren:

free(p);
p = NULL;

free(NULL) ist ausdrücklich so definiert, dass es nichts tut, ein zweites free(p) danach ist also harmlos, und *p wird zu einem sofortigen Absturz an der echten Fehlerstelle statt zu stiller Korruption.

Ein dynamisches Array, von Anfang bis Ende

Hier ist die Gestalt einer echten Belegung: eine Anzahl zur Laufzeit lesen, den Block danach bemessen, ihn benutzen, ihn freigeben.

Der Cast der Anzahl nach size_t zählt, sobald die Größen wachsen: n * sizeof *values mit einem schlichten int n kann überlaufen, bevor die Multiplikation malloc überhaupt erreicht, und einen viel kleineren Block als beabsichtigt erzeugen. In size_t zu multiplizieren vermeidet das.

Innerhalb einer Funktion belegen

Die Lebensdauerregel des Heaps ist es, die das hier erlaubt macht - und die das Zurückgeben eines lokalen Arrays unerlaubt macht:

len + 1 lässt Platz für den Nullterminator - dieselbe Größenregel wie überall bei Zeichenketten. Und beachte den Kommentar über der Funktion: Wenn eine Funktion belegten Speicher zurückgibt, ist die Frage, wer ihn freigibt, Teil ihres Vertrags. Das aufzuschreiben ist keine Bürokratie; es ist das Einzige, was ein Leck verhindert.

Strukturen auf dem Heap

Dasselbe Idiom funktioniert für jeden Typ:

Beachte, dass der zweite Fehlerzweig p freigibt, bevor er zurückkehrt. Sobald eine Funktion mehr als eine Belegung hält, muss jeder Fehlerpfad die bereits genommenen freigeben - die häufigste Quelle der Lecks, die unter Speicherlecks beschrieben werden.

Checkliste

  • Prüfe malloc vor der ersten Nutzung immer auf NULL.
  • Bemiss mit n * sizeof *p und multipliziere in size_t.
  • In C kein Cast auf den Rückgabewert.
  • malloc-Speicher ist uninitialisiert; schreibe, bevor du liest.
  • Gib genau einmal frei, mit dem ursprünglichen Zeiger, und setze ihn dann auf NULL.
  • Jeder Fehlerpfad nach einer Belegung muss freigeben, was er bereits hält.

Häufig gestellte Fragen

Was macht malloc in C?

malloc(n) fordert n Bytes Speicher vom Heap an und liefert einen Zeiger auf den Anfang dieses Blocks, oder NULL, wenn die Anfrage nicht erfüllt werden kann. Der Block lebt, bis du genau diesen Zeiger an free übergibst - anders als eine lokale Variable wird er nicht freigegeben, wenn die Funktion zurückkehrt.

Wie benutzt man malloc und free in C?

Belege mit int *p = malloc(n * sizeof *p);, prüfe if (p == NULL), bevor du ihn anfasst, benutze ihn wie ein Array und rufe dann genau einmal free(p); auf, wenn du fertig bist. Danach p = NULL zu setzen macht aus einer späteren versehentlichen Nutzung einen sauberen Absturz statt stiller Korruption.

Warum ist malloc(n * sizeof *p) besser als malloc(n * sizeof(int))?

Weil sizeof *p dem Typ des Zeigers automatisch folgt. Wird p später ein long * oder ein struct Point *, aktualisiert sich die Belegungsgröße selbst; ein ausgeschriebenes sizeof(int) hinterlässt eine Größe, die nun falsch ist und die der Compiler nicht beanstandet.

Was passiert, wenn man Speicher in C nicht freigibt?

Der Block bleibt für die Lebensdauer des Prozesses belegt - ein Speicherleck. Ein kurzes Programm kommt damit durch, weil das Betriebssystem beim Beenden alles zurückholt, aber ein lange laufendes Programm leckt bei jedem Durchlauf ein wenig und erschöpft irgendwann den Speicher. Siehe Speicherlecks.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S