Menu

calloc und realloc in C: genullter Speicher und wachsende Arrays

calloc liefert genullten Speicher und multipliziert die Größe sicher; realloc vergrößert einen Block, den du bereits gefüllt hast. Diese Seite behandelt beide, das Temporärzeiger-Idiom, das ein fehlgeschlagenes realloc am Lecken hindert, und wann calloc besser ist als malloc plus memset.

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

malloc beantwortet eine Frage: Gib mir so viele Bytes. Zwei Begleiter in <stdlib.h> beantworten die Fragen, die danach kommen - gib mir so viele Bytes, geleert (calloc) und ich brauche den Block, den ich schon habe, größer (realloc).

calloc: Anzahl, Größe und Nullen

void *calloc(size_t count, size_t size);

Zwei Argumente statt eines, und das Ergebnis ist mit Nullbytes gefüllt.

Das ist der klassische Fall: ein Histogramm oder Zählarray, bei dem jeder Platz bei null starten muss. Mit malloc bräuchtest du eine Schleife; calloc erledigt es als Teil der Belegung, oft kostenlos, weil das Betriebssystem frische Seiten ohnehin vorgenullt herausgibt.

Das Überlauf-Argument

Die Aufteilung in zwei Argumente ist nicht kosmetisch. Betrachte eine aus einer Datei gelesene Anzahl:

size_t n = huge_value_from_input;

int *a = malloc(n * sizeof *a);     /* das Produkt kann umschlagen */
int *b = calloc(n, sizeof *b);      /* muss den Überlauf erkennen */

Wenn n * sizeof *a überschreitet, was size_t fassen kann, schlägt es auf eine kleine Zahl um, malloc hat mit einem winzigen Block Erfolg, und jeder nachfolgende Schreibzugriff läuft weit über das Ende hinaus. calloc muss laut Standard stattdessen fehlschlagen und NULL liefern. Wenn eine Größe von außerhalb deines Programms kommt, ist diese Prüfung ihr Geld wert.

Was „genullt" tatsächlich bedeutet

calloc schreibt Null-Bytes. Für Ganzzahlen und Zeichen ist das der Wert null, und das ist, was du willst. Für Zeiger und Gleitkommawerte ist „alle Bits null" auf jeder gängigen Plattform ein Nullzeiger bzw. 0.0, aber der C-Standard verspricht das nicht. Code, der streng portabel sein muss, weist diese ausdrücklich zu.

calloc gegen malloc + memset

int *a = calloc(n, sizeof *a);

int *b = malloc(n * sizeof *b);
if (b != NULL) {
    memset(b, 0, n * sizeof *b);
}

Sie liefern dasselbe Ergebnis. Bevorzuge calloc: Es ist eine Zeile, es macht die Überlaufprüfung, und bei großen Blöcken kann es das Anfassen des Speichers ganz vermeiden. Bevorzuge schlichtes malloc, wenn du ohnehin jedes Byte überschreiben wirst - ein Megabyte zu nullen, das du gleich füllst, ist reine Verschwendung.

realloc: die Größe ändern

void *realloc(void *p, size_t newsize);

realloc liefert einen Block von newsize Bytes, dessen Inhalt bis zur kleineren der beiden Größen dem alten Block entspricht. Es kann den Block dort erweitern, wo er liegt, oder einen neuen belegen, kopieren und den alten freigeben. Du kannst nicht erkennen, was davon, der einzige Zeiger, den du danach benutzen darfst, ist also der zurückgegebene.

Das tmp-Zeiger-Idiom

Die drei Zeilen um tmp sind der ganze Grund, warum diese Funktion einen Ruf hat. Die verlockende Kurzform ist kaputt:

/* BUG: verliert den ursprünglichen Block, wenn realloc fehlschlägt */
p = realloc(p, newsize);
if (p == NULL) {
    return;
}

Wenn realloc fehlschlägt, liefert es NULL und lässt den ursprünglichen Block belegt und unverändert. Das Ergebnis direkt in p zu schreiben überschreibt den einzigen Zeiger auf diesen Block, er kann also nie freigegeben werden - ein Leck, und zwar genau dann, wenn Speicher ohnehin knapp ist. Weise tmp zu, prüfe tmp, und übernimm dann:

void *tmp = realloc(p, newsize);
if (tmp == NULL) {
    /* p ist weiterhin gültig; behandle den Fehler, wie es dem Aufrufer passt */
    return 0;
}
p = tmp;

Zwei weitere Verhaltensweisen sind wissenswert: realloc(NULL, n) verhält sich genau wie malloc(n), wodurch eine Wachstumsfunktion ihren ersten Aufruf ohne Sonderfall behandeln kann. Und das Vergrößern lässt die neuen Bytes uninitialisiert - nur der alte Inhalt bleibt erhalten.

Ein Array vergrößern: das Standardmuster

Hier ist das Muster, das fast jeder C-Container verwendet - eine Länge, eine Kapazität und Verdopplung, wenn sie aufeinandertreffen.

Zwei Entwurfsentscheidungen tragen hier das Gewicht.

Verdoppeln, nicht eins dazuzählen. Um einen festen Betrag zu wachsen lässt jedes Anhängen das ganze Array kopieren, das Füllen von n Elementen kostet also rund Bytekopien. Verdoppeln macht die Kopien selten genug, dass jedes Anhängen im Mittel konstante Zeit kostet. Der Start cap == 0 ? 4 : cap * 2 behandelt das erste Anhängen ohne separaten Zweig, weil realloc(NULL, n) einfach malloc ist.

Die Struktur besitzt eine Belegung. v.data wird genau einmal freigegeben, von dem, der die Struktur besitzt. Diese Besitzregel aufzuschreiben - in einem Kommentar oder indem man jedes vec_init mit einem vec_free paart - ist es, was einen wachsenden Container davon abhält zu lecken, während er zwischen Funktionen wandert.

Verkleinern

realloc mit einer kleineren Größe stutzt einen Block, was nützlich ist, nachdem du eine unbekannte Menge Daten in einen überdimensionierten Puffer gelesen hast:

Ein fehlgeschlagenes Verkleinern ist kein Fehler, für den sich ein Abbruch lohnt: Der alte, größere Block ist weiterhin gültig und weiterhin korrekt. Das ist der eine Fall, in dem es richtig ist, den Fehlschlag zu ignorieren - beachte aber, dass es trotzdem über tmp läuft, denn die Regel, den lebenden Zeiger nicht zu überschreiben, ändert sich nicht.

Wozu man greifen sollte

Was du willstNimm
Bytes, Inhalt egalmalloc(n * sizeof *p)
Eine Anzahl Elemente, auf null gesetztcalloc(n, sizeof *p)
Eine Anzahl aus nicht vertrauenswürdiger Eingabecalloc, wegen der Überlaufprüfung
Den vorhandenen Block, größer oder kleinerrealloc über einen tmp-Zeiger
Erste Belegung in einer Wachstumsschleiferealloc(NULL, n) - kein Sonderfall

Alle vier werden mit demselben free freigegeben, genau einmal.

Häufige Fehler

  • p = realloc(p, n) - leckt bei einem Fehlschlag. Geh immer über tmp.
  • Einen alten Zeiger in den Block behalten. Nach realloc können die Daten verschoben worden sein, jeder gespeicherte Innenzeiger und jede aus einem Index abgeleitete Adresse ist also veraltet. Indizes überleben eine Verschiebung; Zeiger nicht.
  • Annehmen, die neuen Bytes seien null. Nur calloc nullt. realloc lässt den Zuwachsbereich uninitialisiert.
  • calloc(n * size, 1) - das wirft die Überlaufprüfung weg, für die es die zweiargumentige Form gibt.
  • Den alten Zeiger nach einem erfolgreichen realloc freigeben. realloc hat ihn bereits freigegeben; ein zweites free ist ein doppeltes Freigeben.

Häufig gestellte Fragen

Was ist der Unterschied zwischen calloc und malloc in C?

Zwei Unterschiede. calloc(n, size) nimmt Anzahl und Elementgröße getrennt entgegen und prüft, dass ihre Multiplikation nicht überläuft, während malloc(n * size) still umschlagen kann. Und calloc füllt den Block mit Nullen, während malloc ihn so lässt, wie er vorher war.

Wie funktioniert realloc in C?

realloc(p, newsize) liefert einen Block der neuen Größe, in dem dein bisheriger Inhalt bis zur kleineren der beiden Größen erhalten bleibt. Es kann den Block an Ort und Stelle vergrößern oder ihn verschieben, der zurückgegebene Zeiger ist also der einzige, den du weiterverwenden darfst - der alte kann bereits freigegeben sein.

Warum sollte man realloc einem temporären Zeiger zuweisen?

Weil p = realloc(p, n) bei einem Fehlschlag p mit NULL überschreibt, während der ursprüngliche Block weiterhin belegt ist und nichts mehr auf ihn zeigt - ein Leck. Weise zuerst einem tmp zu, prüfe es auf NULL, und schreibe erst dann p = tmp.

Wann sollte ich calloc statt malloc verwenden?

Wenn du den Speicher wirklich genullt brauchst (ein Zählarray, eine Struktur, deren Felder leer starten sollen, ein Puffer, den du nur teilweise füllst) oder wenn die Größe eine Anzahl mal eine Elementgröße ist, die groß werden könnte. Wenn du ohnehin jedes Byte überschreibst, spart malloc die Nullungsarbeit.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S