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?
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?
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.
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.
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.
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
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.
@R.. interessante Anmerkung. Aber in der Praxis, gibt es solche Implementierungen in freier Wildbahn?
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.
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.
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
@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.
Es dient wahrscheinlich nur zur Veranschaulichung. Denn der Compiler wird das wahrscheinlich sowieso wegoptimieren. Meine kompiliert in diese asm: push 1.
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/
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.
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.
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.
@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 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.
48 Stimmen
In C wirft man das Ergebnis von
malloc
Familie9 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
2 Stimmen
@ddddavidee Auch ich habe diesen Blog gefunden, nachdem ich mit so vielen Antworten im Netz unzufrieden war. Nathaniel J. Smith verdient 100+ SO Punkte für seine Analyse.
0 Stimmen
Verwandt: Kann calloc() insgesamt mehr als SIZE_MAX zuweisen?