Verwendet Malloc den Heap nur, wenn der angeforderte Speicherplatz groß ist?
Lesezeit: 8 Minuten
Daniel Scocco
Wann immer Sie die Speicherzuweisung von Prozessen untersuchen, sehen Sie sie normalerweise so umrissen:
So weit, ist es gut.
Aber dann haben Sie den Systemaufruf sbrk(), der es dem Programm ermöglicht, die Obergrenze von its zu ändern Datenbereich, und es kann auch verwendet werden, um einfach mit sbrk(0) zu überprüfen, wo diese Grenze liegt. Mit dieser Funktion fand ich die folgenden Muster:
Muster 1 – Kleiner Malloc
Ich führe das folgende Programm auf meinem Linux-Rechner aus:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globalVar;
int main(){
int localVar;
int *ptr;
printf("localVar address (i.e., stack) = %p\n",&localVar);
printf("globalVar address (i.e., data section) = %p\n",&globalVar);
printf("Limit of data section = %p\n",sbrk(0));
ptr = malloc(sizeof(int)*1000);
printf("ptr address (should be on stack)= %p\n",&ptr);
printf("ptr points to: %p\n",ptr);
printf("Limit of data section after malloc= %p\n",sbrk(0));
return 0;
}
Und die Ausgabe ist die folgende:
localVar address (i.e., stack) = 0xbfe34058
globalVar address (i.e., data section) = 0x804a024
Limit of data section = 0x91d9000
ptr address (should be on stack)= 0xbfe3405c
ptr points to: 0x91d9008
Limit of data section after malloc= 0x91fa000
Wie Sie sehen können, lag der zugewiesene Speicherbereich direkt über der alten Datenabschnittsgrenze, und nach dem Malloc wurde diese Grenze nach oben verschoben, sodass sich die zugewiesene Region tatsächlich innerhalb des neuen Datenabschnitts befindet.
Frage 1: Bedeutet dies, dass kleine Mallocs Speicher im Datenabschnitt zuweisen und den Heap überhaupt nicht verwenden?
Muster 2 – Großer Malloc
Wenn Sie die angeforderte Speichergröße in Zeile 15 erhöhen:
ptr = malloc(sizeof(int)*100000);
Sie erhalten nun die folgende Ausgabe:
localVar address (i.e., stack) = 0xbf93ba68
globalVar address (i.e., data section) = 0x804a024
Limit of data section = 0x8b16000
ptr address (should be on stack)= 0xbf93ba6c
ptr points to: 0xb750b008
Limit of data section after malloc= 0x8b16000
Wie Sie hier sehen können, hat sich die Grenze des Datenabschnitts nicht geändert, und stattdessen befindet sich der zugewiesene Speicherbereich in der Mitte des Lückenabschnitts zwischen dem Datenabschnitt und dem Stapel.
Frage 2: Nutzt der große Malloc tatsächlich den Heap?
Frage 3: Irgendeine Erklärung für dieses Verhalten? Ich finde es ein bisschen unsicher, denn beim ersten Beispiel (kleines Malloc) können Sie auch nach dem Freigeben des zugewiesenen Speichers den Zeiger verwenden und diesen Speicher verwenden, ohne einen Seg-Fehler zu erhalten, da er sich in Ihren Daten befindet Abschnitt, und dies könnte zu schwer zu erkennenden Fehlern führen.
Update mit Spezifikationen: Ubuntu 12.04, 32-Bit, gcc-Version 4.6.3, Linux-Kernel 3.2.0-54-generic-pae.
Update 2: Rodrigos Antwort unten löste dieses Rätsel. Dieser Wikipedia-Link hat auch geholfen.
All diese Fragen “wird Zeug X passieren, wenn Zeug Y passiert” sind theoretisch und praktisch nicht zu beantworten, ohne eine bestimmte Implementierung zu erwähnen. Welches Linux? Welcher Compiler? Welche Standardbibliotheksimplementierung? Welche CPU?
– Benutzer529758
10. Oktober 2013 um 19:49 Uhr
@H2CO3, sagen Sie also, dass Sie sicher sind, dass das obige Verhalten implementierungsabhängig und nicht beispielsweise der Standard des Linux-Kernels ist? Denn wenn dies der Standard des Linux-Kernels wäre, würden die Spezifikationen keine Rolle spielen, oder? Auf jeden Fall habe ich sie der Vollständigkeit halber eingefügt.
– Daniel Scocco
10. Oktober 2013 um 19:54 Uhr
@ H2CO3 Ich stimme zu. Ich finde das Verhalten trotzdem merkwürdig (nicht wahr?), also mal sehen, ob jemand mehr Hinweise darauf hat.
– Daniel Scocco
10. Oktober 2013 um 19:58 Uhr
Mein Verständnis ist das malloc führt die Speicherverwaltung von Heap im Benutzerbereich durch – Freigeben oder Anfordern von Speicherblöcken vom Betriebssystem nach Bedarf (dh Versuch, teure Kontextwechsel zu reduzieren). Ich denke auch, dass malloc Speicherblöcke anfordert, die mit diesem Betriebssystem/dieser Hardware belegbar sind.
– Ed heilen
10. Oktober 2013 um 20:03 Uhr
Denken Sie daran, dass viele Heap-Manager extrem große Zuordnungen (im Allgemeinen über 16 MB oder so) in einem anderen “Segment” als den Rest platzieren, mit einem sichtbar anderen Satz von höherwertigen Bits in der Adresse. Und es ist nicht ungewöhnlich, dass sich Stack und Heap in unterschiedlichen Segmenten befinden. Das obige Diagramm ist angenehm einfach und prägnant (und eine gute konzeptionelle Ansicht), spiegelt aber oft nicht die Realität wider.
– Heiße Licks
10. Oktober 2013 um 20:04 Uhr
rodrigo
Zunächst einmal ist die einzige Möglichkeit, absolut sicher zu sein, was passiert, den Quellcode von zu lesen malloc. Oder noch besser, gehen Sie es mit dem Debugger durch.
Aber wie auch immer, hier ist mein Verständnis dieser Dinge:
Der Systemaufruf sbrk() wird verwendet, um die Größe des Datenabschnitts zu erhöhen, in Ordnung. Normalerweise werden Sie es nicht direkt anrufen, aber es wird von der angerufen Implementierung von malloc() um den für den Heap verfügbaren Speicher zu erhöhen.
Die Funktion malloc() weist keinen Speicher vom Betriebssystem zu. Es teilt den Datenabschnitt einfach in Teile auf und weist diese Teile denjenigen zu, die sie benötigen. Sie nutzen free() um ein Stück als unbenutzt und für eine Neuzuweisung verfügbar zu markieren.
Punkt 2 ist eine zu starke Vereinfachung. Zumindest die GCC-Implementierung für große Blöcke, malloc() ordnet sie mit zu mmap() mit privaten, nicht dateigestützten Optionen. Somit liegen diese Blöcke außerhalb des Datensegments. Natürlich anrufen free() in einem solchen Block wird anrufen munmap().
Was genau ist ein großer Block hängt von vielen Details ab. Sehen man mallopt für die blutigen Details.
Daraus können Sie erraten, was passiert, wenn Sie auf den freien Speicher zugreifen:
Wenn der Block war klein, der Speicher ist immer noch da, wenn Sie also lesen, passiert nichts. Wenn Sie darauf schreiben, können Sie die internen Heap-Strukturen beschädigen, oder es wurde möglicherweise wiederverwendet, und Sie können jede zufällige Struktur beschädigen.
Wenn der Block groß war, wurde der Speicher nicht zugeordnet, sodass jeder Zugriff zu einem Segmentierungsfehler führt. Es sei denn, die unwahrscheinliche Situation, dass in der Zwischenzeit ein weiterer großer Block zugewiesen wird (oder ein anderer Thread aufruft mmap() und der gleiche Adressbereich verwendet werden.
Klärung
Der Begriff Datenbereich wird je nach Kontext mit zwei verschiedenen Bedeutungen verwendet.
Das .data Abschnitt der ausführbaren Datei (Linker-Sicht). Es kann auch beinhalten .bss oder auch .rdata. Für das Betriebssystem bedeutet das nichts, es bildet nur Teile des Programms im Speicher ab, ohne Rücksicht darauf, was es außer den Flags enthält (schreibgeschützt, ausführbar …).
Das Haufendieser Speicherblock, den jeder Prozess hat, der nicht aus der ausführbaren Datei gelesen wird und der mit erweitert werden kann sbrk().
Sie können das mit dem folgenden Befehl sehen, der das Speicherlayout eines einfachen Programms ausgibt (cat):
Die erste Zeile ist der ausführbare Code (.text Sektion).
Die zweite Zeile sind die schreibgeschützten Daten (.rdata Abschnitt) und einige andere schreibgeschützte Abschnitte.
Die dritte Zeile ist die .data + .bss und einige andere beschreibbare Abschnitte.
Die vierte Zeile ist der Haufen!
Die nächsten Zeilen, die mit einem Namen, sind speicherabgebildete Dateien oder gemeinsam genutzte Objekte. Diejenigen ohne Namen sind wahrscheinlich große Speicherblöcke (oder vielleicht private anonyme mmaps, sie sind unmöglich zu unterscheiden).
Die letzte Zeile ist der Stack!
In Punkt 1 sagen Sie, dass sbrk() verwendet wird, um die Größe des “Datenabschnitts” zu erhöhen, aber dass die Funktion sbrk() von malloc() aufgerufen wird, um den verfügbaren Speicher “für den Heap” zu erhöhen. Es ist also nicht klar. Wollen Sie damit sagen, dass sbrk() auf den Datenabschnitt oder auf den Heap wirkt? Oder sagen Sie, dass sie unter einem praktischen Gesichtspunkt dasselbe sind?
– Daniel Scocco
10. Oktober 2013 um 20:11 Uhr
Stimmen Sie auch zu, dass Sie sagen könnten, dass der “Heap” ist, wenn Sie über mmap() große Speicherblöcke zuweisen, und dass für kleine Brocken nicht der “Heap” verwendet wird, sondern der “Datenabschnitt”?
– Daniel Scocco
10. Oktober 2013 um 20:19 Uhr
@DanielS: Entschuldigung, ich war nicht sehr klar. Bitte sehen Sie sich mein hinzugefügtes Beispiel an.
– rodrigo
10. Oktober 2013 um 20:29 Uhr
@DanielS: Nein! Der Heap ist der Speicherblock, der von verwendet wird malloc für kleine Blöcke, und es ist auch die Datenstruktur, die verwendet wird, um diese Blöcke zu verwalten. mmap() verwendet einfach nicht zugeordneten Speicherplatz, und die Strukturen, die zu deren Verwaltung verwendet werden, befinden sich im Kernel-VMM. Beachten Sie auch, dass die Heap-Struktur vollständig im Code der User-Space-Bibliothek implementiert ist.
– rodrigo
10. Oktober 2013 um 20:32 Uhr
gotcha, und macht jetzt Sinn. Wenn also die Manpage von sbrk() von “Datenabschnitt” spricht, spricht sie eigentlich vom “Heap”, richtig? Und sbrk(0) gibt tatsächlich die Spitze des Haufens zurück? Ich war in der Tat mit dem Datenabschnitt der ausführbaren Datei verwirrend. Jetzt macht es Sinn, entweder den malloc auf dem Heap allokieren, oder wenn zu groß per mmpa() allozieren. Ist das jetzt richtig?
– Daniel Scocco
10. Oktober 2013 um 20:36 Uhr
11010000cookie-checkVerwendet Malloc den Heap nur, wenn der angeforderte Speicherplatz groß ist?yes
All diese Fragen “wird Zeug X passieren, wenn Zeug Y passiert” sind theoretisch und praktisch nicht zu beantworten, ohne eine bestimmte Implementierung zu erwähnen. Welches Linux? Welcher Compiler? Welche Standardbibliotheksimplementierung? Welche CPU?
– Benutzer529758
10. Oktober 2013 um 19:49 Uhr
@H2CO3, sagen Sie also, dass Sie sicher sind, dass das obige Verhalten implementierungsabhängig und nicht beispielsweise der Standard des Linux-Kernels ist? Denn wenn dies der Standard des Linux-Kernels wäre, würden die Spezifikationen keine Rolle spielen, oder? Auf jeden Fall habe ich sie der Vollständigkeit halber eingefügt.
– Daniel Scocco
10. Oktober 2013 um 19:54 Uhr
@ H2CO3 Ich stimme zu. Ich finde das Verhalten trotzdem merkwürdig (nicht wahr?), also mal sehen, ob jemand mehr Hinweise darauf hat.
– Daniel Scocco
10. Oktober 2013 um 19:58 Uhr
Mein Verständnis ist das
malloc
führt die Speicherverwaltung von Heap im Benutzerbereich durch – Freigeben oder Anfordern von Speicherblöcken vom Betriebssystem nach Bedarf (dh Versuch, teure Kontextwechsel zu reduzieren). Ich denke auch, dass malloc Speicherblöcke anfordert, die mit diesem Betriebssystem/dieser Hardware belegbar sind.– Ed heilen
10. Oktober 2013 um 20:03 Uhr
Denken Sie daran, dass viele Heap-Manager extrem große Zuordnungen (im Allgemeinen über 16 MB oder so) in einem anderen “Segment” als den Rest platzieren, mit einem sichtbar anderen Satz von höherwertigen Bits in der Adresse. Und es ist nicht ungewöhnlich, dass sich Stack und Heap in unterschiedlichen Segmenten befinden. Das obige Diagramm ist angenehm einfach und prägnant (und eine gute konzeptionelle Ansicht), spiegelt aber oft nicht die Realität wider.
– Heiße Licks
10. Oktober 2013 um 20:04 Uhr