949 Stimmen

Unterschied zwischen malloc und calloc?

Was ist der Unterschied zwischen:

ptr = malloc (MAXELEMS * sizeof(char *));

oder:

ptr = calloc (MAXELEMS, sizeof(char*));

Wann ist es eine gute Idee, calloc statt malloc zu verwenden oder umgekehrt?

48 Stimmen

9 Stimmen

In C könnte man die obige Aufgabe allgemeiner formulieren als: ptr = calloc(MAXELEMS, sizeof(*ptr));

8 Stimmen

Ein interessanter Beitrag über den Unterschied zwischen calloc und malloc+memset vorpus.org/blog/warum-besteht-calloc-exist

1014voto

Fred Larson Punkte 58721

calloc() gibt Ihnen einen null-initialisierten Puffer, während malloc() lässt den Speicher uninitialisiert.

Bei großen Zuweisungen werden die meisten calloc Implementierungen unter gängigen Betriebssystemen erhalten vom Betriebssystem bekannte Nullseiten (z. B. über POSIX mmap(MAP_ANONYMOUS) oder Windows VirtualAlloc ), so dass sie nicht in den User-Space geschrieben werden müssen. So wird die normale malloc holt auch mehr Seiten aus dem Betriebssystem; calloc nutzt einfach die Garantie des Betriebssystems.

Dies bedeutet calloc Speicher kann immer noch "sauber" und "lazily-allocated" sein, und "copy-on-write" auf eine systemweit gemeinsam genutzte physische Seite mit Nullen abgebildet werden. (Unter der Annahme eines Systems mit virtuellem Speicher.)

Einige Compiler können sogar malloc + memset(0) in calloc für Sie optimieren, aber Sie sollten calloc explizit verwenden, wenn Sie den Speicher als 0 .

Wenn Sie den Speicher vor dem Schreiben nicht mehr lesen wollen, verwenden Sie malloc so dass es Ihnen (möglicherweise) schmutzigen Speicher aus seiner internen freien Liste geben kann, anstatt neue Seiten vom Betriebssystem zu bekommen. (Oder anstatt einen Speicherblock in der freien Liste für eine kleine Zuweisung auf Null zu setzen).


Eingebettete Implementierungen von calloc kann es überlassen calloc selbst auf Null setzen, wenn kein Betriebssystem vorhanden ist oder es sich nicht um ein ausgefallenes Mehrbenutzer-Betriebssystem handelt, das Seiten auf Null setzt, um Informationslecks zwischen Prozessen zu verhindern.

Unter Embedded Linux könnte malloc mmap(MAP_UNINITIALIZED|MAP_ANONYMOUS) die nur für einige eingebettete Kernel aktiviert ist, weil sie auf einem Mehrbenutzersystem unsicher ist.

265 Stimmen

Die *alloc-Varianten sind ziemlich einprägsam - clear-alloc, memory-alloc, re-alloc.

52 Stimmen

Verwenden Sie malloc(), wenn Sie alles, was Sie verwenden, in den zugewiesenen Speicherplatz setzen wollen. Verwenden Sie calloc(), wenn Sie Teile der Daten uninitialisiert lassen wollen - und es wäre vorteilhaft, die nicht gesetzten Teile auf Null zu setzen.

286 Stimmen

calloc ist nicht unbedingt teurer, da das Betriebssystem einige Tricks anwenden kann, um es zu beschleunigen. Ich weiß, dass FreeBSD, wenn es freie CPU-Zeit hat, diese nutzt, um einen einfachen Prozess laufen zu lassen, der einfach herumläuft und freigegebene Speicherblöcke löscht und Blöcke mit einem Flag markiert. Wenn Sie also calloc Wenn Sie einen solchen Block suchen, versucht er zunächst, einen solchen Block zu finden und ihn Ihnen zu geben - und höchstwahrscheinlich wird er auch einen finden.

397voto

Isak Savo Punkte 33709

Ein weniger bekannter Unterschied ist, dass in Betriebssystemen mit optimistischer Speicherzuweisung, wie Linux, der Zeiger, der von malloc wird nicht durch echten Speicher unterstützt, bis das Programm ihn tatsächlich berührt.

calloc den Speicher tatsächlich berührt (es schreibt Nullen darauf) und so können Sie sicher sein, dass das Betriebssystem die Zuweisung mit tatsächlichem RAM (oder Swap) unterstützt. Dies ist auch der Grund, warum es langsamer ist als malloc (es muss nicht nur Nullen schreiben, sondern das Betriebssystem muss auch einen geeigneten Speicherbereich finden, indem es möglicherweise andere Prozesse auslagert)

Siehe zum Beispiel diese SO-Frage für weitere Diskussionen über das Verhalten von malloc

59 Stimmen

calloc müssen keine Nullen schreiben. Wenn der zugewiesene Block größtenteils aus neuen Nullseiten besteht, die vom Betriebssystem bereitgestellt werden, kann es diese unberührt lassen. Dies erfordert natürlich calloc auf das Betriebssystem abgestimmt werden, anstatt eine generische Bibliotheksfunktion auf der Basis von malloc . Oder ein Implementierer könnte calloc jedes Wort mit Null zu vergleichen, bevor es genullt wird. Dies würde keine Zeit sparen, aber es würde verhindern, dass die neuen Seiten verschmutzt werden.

3 Stimmen

@R.. interessante Anmerkung. Aber in der Praxis, gibt es solche Implementierungen in freier Wildbahn?

12 Stimmen

Alle dlmalloc -ähnliche Implementierungen überspringen die memset wenn der Chunk über mmap neue anonyme Seiten (oder gleichwertig) zu erstellen. Normalerweise wird diese Art der Zuweisung für größere Brocken verwendet, beginnend bei 256k oder so. Ich kenne keine Implementierungen, die den Vergleich gegen Null durchführen, bevor sie Null schreiben, abgesehen von meiner eigenen.

128voto

Ein oft übersehener Vorteil von calloc ist, dass es (konforme Implementierungen) zum Schutz vor Integer-Überlauf-Schwachstellen beitragen wird. Vergleichen Sie:

size_t count = get_int32(file);
struct foo *bar = malloc(count * sizeof *bar);

vs.

size_t count = get_int32(file);
struct foo *bar = calloc(count, sizeof *bar);

Ersteres könnte zu einer winzigen Zuweisung und anschließenden Pufferüberläufen führen, wenn count größer ist als SIZE_MAX/sizeof *bar . Letzteres wird in diesem Fall automatisch fehlschlagen, da ein so großes Objekt nicht erstellt werden kann.

Natürlich müssen Sie auf nicht konforme Implementierungen achten, die die Möglichkeit eines Überlaufs einfach ignorieren... Wenn dies auf den Plattformen, auf die Sie abzielen, ein Problem darstellt, müssen Sie ohnehin einen manuellen Test auf Überlauf durchführen.

24 Stimmen

Offenbar war der arithmetische Überlauf die Ursache für das OpenSSH-Loch im Jahr 2002. Guter Artikel von OpenBSD über die Gefahren dieses Problems mit speicherbezogenen Funktionen: undeadly.org/cgi?action=article&sid=20060330071917

5 Stimmen

@KomradeP.: Interessant. Leider enthält der von Ihnen verlinkte Artikel gleich zu Beginn eine Fehlinformation. Das Beispiel mit char es pas einen Überlauf, sondern eine implementierungsdefinierte Konvertierung bei der Rückübertragung des Ergebnisses in eine char Objekt.

0 Stimmen

Es dient wahrscheinlich nur zur Veranschaulichung. Denn der Compiler wird das wahrscheinlich sowieso wegoptimieren. Meine kompiliert in diese asm: push 1.

43voto

t0rakka Punkte 814

Die Dokumentation macht die calloc 似る malloc die lediglich eine Null-Initialisierung des Speichers vornimmt; dies ist nicht der Hauptunterschied! Die Idee von calloc ist die Abstraktion der Copy-on-Write-Semantik für die Speicherzuweisung. Wenn Sie Speicher zuweisen mit calloc alle auf dieselbe physische Seite abgebildet werden, die auf Null initialisiert ist. Wenn eine der Seiten des zugewiesenen Speichers in eine physische Seite geschrieben wird, wird diese zugewiesen. Dies wird häufig verwendet, um HUGE Hash-Tabellen zu erstellen, da die leeren Teile des Hash nicht durch zusätzlichen Speicher (Seiten) gesichert sind; sie verweisen einfach auf die einzige null-initialisierte Seite, die sogar zwischen Prozessen geteilt werden kann.

Jedes Schreiben auf eine virtuelle Adresse wird einer Seite zugeordnet, wenn diese Seite die Null-Seite ist, wird eine andere physische Seite zugewiesen, die Null-Seite wird dorthin kopiert und der Kontrollfluss wird an den Client-Prozess zurückgegeben. Dies funktioniert auf dieselbe Art und Weise wie speicherzugeordnete Dateien, virtueller Speicher usw es wird Paging verwendet.

Hier ist eine Optimierungsgeschichte zum Thema: http://blogs.fau.de/hager/2007/05/08/benchmarking-fun-with-calloc-and-zero-pages/

27voto

AnT Punkte 300728

Es gibt keinen Unterschied in der Größe des zugewiesenen Speicherblocks. calloc füllt den Speicherblock einfach mit dem physischen Muster der Null-Bits. In der Praxis wird oft davon ausgegangen, dass die Objekte im Speicherblock, der mit calloc haben einen Anfangswert, als ob sie mit einem Literal initialisiert würden 0 , d.h. ganze Zahlen sollten den Wert von 0 Gleitkomma-Variablen - Wert von 0.0 , Zeiger - der entsprechende Null-Zeiger-Wert usw.

Vom pedantischen Standpunkt aus betrachtet, calloc (sowie memset(..., 0, ...) ) ist nur gewährleistet, dass Objekte des Typs unsigned char . Für alles andere wird nicht garantiert, dass es ordnungsgemäß initialisiert ist, und es kann so genannte Trap-Darstellung was zu undefiniertem Verhalten führt. Mit anderen Worten, für jeden anderen Typ als unsigned char das oben erwähnte Muster mit nur Null-Bits könnte einen illegalen Wert darstellen, Trap-Darstellung.

Später, in einer der technischen Korrekturen zum C99-Standard, wurde das Verhalten für alle Integer-Typen definiert (was Sinn macht). D.h. formal kann man in der aktuellen Sprache C nur Integer-Typen initialisieren mit calloc (und memset(..., 0, ...) ). Die Verwendung zur Initialisierung von etwas anderem führt in der Regel zu undefiniertem Verhalten aus der Sicht der Sprache C.

In der Praxis, calloc funktioniert, wie wir alle wissen :), aber ob Sie es verwenden möchten (in Anbetracht der oben genannten Punkte), liegt an Ihnen. Ich persönlich ziehe es vor, es komplett zu vermeiden und verwende malloc und führe stattdessen meine eigene Initialisierung durch.

Ein weiteres wichtiges Detail ist schließlich, dass calloc ist erforderlich, um die endgültige Blockgröße zu berechnen intern durch Multiplikation der Elementgröße mit der Anzahl der Elemente. Dabei wird, calloc muss auf einen möglichen arithmetischen Überlauf achten. Er führt zu einer erfolglosen Zuweisung (Null-Zeiger), wenn die angeforderte Blockgröße nicht korrekt berechnet werden kann. Inzwischen hat Ihr malloc Version unternimmt keinen Versuch, auf Überlauf zu achten. Es wird eine "unvorhersehbare" Menge an Speicher zugewiesen, falls ein Überlauf auftritt.

0 Stimmen

Zu dem Absatz "ein weiteres wichtiges Detail": das scheint zu sein memset(p, v, n * sizeof type); ein Problem, weil n * sizeof type kann überlaufen. Ich schätze, ich muss eine for(i=0;i<n;i++) p[i]=v; Schleife für robusten Code.

0 Stimmen

Es wäre hilfreich, wenn es ein Standardmittel gäbe, mit dem Code behaupten könnte, dass eine Implementierung all-bits-zero als Null-Zeiger verwenden muss (und andernfalls die Kompilierung verweigert), da es Implementierungen gibt, die andere Null-Zeiger-Darstellungen verwenden, aber sie sind vergleichsweise selten; Code, der nicht auf solchen Implementierungen laufen muss, kann schneller sein, wenn er calloc() oder memset verwenden kann, um Arrays von Zeigern zu initialisieren.

0 Stimmen

@chux Nein, wenn ein Array mit n Elemente existieren, wobei ein Element die Größe sizeof type entonces n*sizeof type kann nicht überlaufen, da die maximale Größe eines Objekts kleiner sein muss als SIZE_MAX .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X