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 n² 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 willst | Nimm |
|---|---|
| Bytes, Inhalt egal | malloc(n * sizeof *p) |
| Eine Anzahl Elemente, auf null gesetzt | calloc(n, sizeof *p) |
| Eine Anzahl aus nicht vertrauenswürdiger Eingabe | calloc, wegen der Überlaufprüfung |
| Den vorhandenen Block, größer oder kleiner | realloc über einen tmp-Zeiger |
| Erste Belegung in einer Wachstumsschleife | realloc(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 übertmp.- Einen alten Zeiger in den Block behalten. Nach
reallockö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
callocnullt.realloclä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
reallocfreigeben.reallochat ihn bereits freigegeben; ein zweitesfreeist 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.