Ich möchte wissen wie malloc
und free
arbeiten.
int main() {
unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
memset(p,0,4);
strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
cout << p;
free(p); // Obvious Crash, but I need how it works and why crash.
cout << p;
return 0;
}
Ich wäre wirklich dankbar, wenn die Antwort auf Speicherebene ausführlich wäre, wenn es möglich ist.
malloc
cout <<
Antworten:
OK, einige Antworten zu malloc wurden bereits veröffentlicht.
Der interessantere Teil ist, wie frei funktioniert (und in dieser Richtung kann auch Malloc besser verstanden werden).
In vielen malloc / free-Implementierungen gibt free normalerweise den Speicher nicht an das Betriebssystem zurück (oder zumindest nur in seltenen Fällen). Der Grund ist, dass Sie Lücken in Ihrem Heap bekommen und es daher vorkommen kann, dass Sie nur Ihre 2 oder 4 GB virtuellen Speicher mit Lücken fertigstellen. Dies sollte vermieden werden, da Sie, sobald der virtuelle Speicher fertig ist, in große Schwierigkeiten geraten. Der andere Grund ist, dass das Betriebssystem nur Speicherblöcke verarbeiten kann, die eine bestimmte Größe und Ausrichtung haben. Um genau zu sein: Normalerweise kann das Betriebssystem nur Blöcke verarbeiten, die der virtuelle Speichermanager verarbeiten kann (meistens Vielfache von 512 Bytes, z. B. 4 KB).
Die Rückgabe von 40 Bytes an das Betriebssystem funktioniert also einfach nicht. Was macht Free?
Free setzt den Speicherblock in eine eigene freie Blockliste. Normalerweise wird auch versucht, benachbarte Blöcke im Adressraum miteinander zu verschmelzen. Die freie Blockliste ist nur eine kreisförmige Liste von Speicherblöcken, die am Anfang einige Verwaltungsdaten enthalten. Dies ist auch der Grund, warum die Verwaltung sehr kleiner Speicherelemente mit dem Standard malloc / free nicht effizient ist. Jeder Speicherblock benötigt zusätzliche Daten und bei kleineren Größen tritt eine stärkere Fragmentierung auf.
Die freie Liste ist auch der erste Ort, den Malloc betrachtet, wenn ein neuer Speicherblock benötigt wird. Es wird gescannt, bevor es neuen Speicher vom Betriebssystem anfordert. Wenn ein Block gefunden wird, der größer als der benötigte Speicher ist, wird er in zwei Teile geteilt. Einer wird an den Anrufer zurückgegeben, der andere wird wieder in die kostenlose Liste aufgenommen.
Es gibt viele verschiedene Optimierungen für dieses Standardverhalten (z. B. für kleine Speicherblöcke). Aber da malloc und free so universell sein müssen, ist das Standardverhalten immer der Fallback, wenn Alternativen nicht verwendbar sind. Es gibt auch Optimierungen bei der Handhabung der freien Liste - zum Beispiel das Speichern der Chunks in Listen, die nach Größen sortiert sind. Alle Optimierungen haben aber auch ihre eigenen Grenzen.
Warum stürzt Ihr Code ab:
Der Grund dafür ist, dass Sie durch Schreiben von 9 Zeichen (vergessen Sie nicht das nachfolgende Null-Byte) in einen Bereich mit einer Größe von 4 Zeichen wahrscheinlich die Verwaltungsdaten überschreiben, die für einen anderen Speicherblock gespeichert sind, der sich "hinter" Ihrem Datenblock befindet ( da diese Daten meistens "vor" den Speicherblöcken gespeichert werden). Wenn free dann versucht, Ihren Chunk in die freie Liste aufzunehmen, kann es diese Verwaltungsdaten berühren und daher über einen überschriebenen Zeiger stolpern. Dies wird das System zum Absturz bringen.
Dies ist ein ziemlich anmutiges Verhalten. Ich habe auch Situationen gesehen, in denen ein außer Kontrolle geratener Zeiger irgendwo Daten in der speicherfreien Liste überschrieben hat und das System nicht sofort abstürzte, sondern einige Unterprogramme später. Selbst in einem System mittlerer Komplexität können solche Probleme sehr, sehr schwer zu debuggen sein! In dem einen Fall, in dem ich involviert war, haben wir (eine größere Gruppe von Entwicklern) mehrere Tage gebraucht, um den Grund für den Absturz zu finden - da er sich an einem völlig anderen Ort befand als dem, der durch den Speicherauszug angegeben wurde. Es ist wie eine Zeitbombe. Sie wissen, Ihr nächstes "freies" oder "malloc" wird abstürzen, aber Sie wissen nicht warum!
Dies sind einige der schlimmsten C / C ++ - Probleme und ein Grund, warum Zeiger so problematisch sein können.
quelle
Wie Aluser in diesem Forenthread sagt :
malloc () ist system- / compilerabhängig, daher ist es schwierig, eine bestimmte Antwort zu geben. Grundsätzlich wird jedoch nachverfolgt, welcher Speicher zugewiesen ist, und je nachdem, wie dies geschieht, können Ihre Aufrufe zum Freigeben fehlschlagen oder erfolgreich sein.
malloc() and free() don't work the same way on every O/S.
quelle
Eine Implementierung von malloc / free führt Folgendes aus:
quelle
Der Speicherschutz ist seitengranular und erfordert eine Kernel-Interaktion
Ihr Beispielcode fragt im Wesentlichen, warum das Beispielprogramm nicht abgefangen wird, und die Antwort lautet, dass der Speicherschutz eine Kernelfunktion ist und nur für ganze Seiten gilt, während der Speicherzuweiser eine Bibliotheksfunktion ist und .. ohne Durchsetzung .. willkürlich verwaltet große Blöcke, die oft viel kleiner als Seiten sind.
Speicher kann nur in Seiteneinheiten aus Ihrem Programm entfernt werden, und selbst das ist unwahrscheinlich.
calloc (3) und malloc (3) interagieren mit dem Kernel, um bei Bedarf Speicher zu erhalten. Die meisten Implementierungen von free (3) geben jedoch keinen Speicher an den Kernel 1 zurück , sondern fügen ihn einfach einer freien Liste hinzu, die calloc () und malloc () später abrufen, um die freigegebenen Blöcke wiederzuverwenden.
Selbst wenn ein free () Speicher an das System zurückgeben wollte, würde es mindestens eine zusammenhängende Speicherseite benötigen, damit der Kernel die Region tatsächlich schützt. Das Freigeben eines kleinen Blocks würde also nur dann zu einer Schutzänderung führen, wenn dies der Fall wäre der letzte kleine Block auf einer Seite.
Ihr Block ist also da und sitzt auf der freien Liste. Sie können fast immer darauf und auf den Speicher in der Nähe zugreifen, als wäre er noch zugewiesen. C kompiliert direkt zum Maschinencode und ohne spezielle Debugging-Vorkehrungen gibt es keine Überprüfung der Integrität von Ladungen und Speichern. Wenn Sie nun versuchen, auf einen freien Block zuzugreifen, ist das Verhalten vom Standard nicht definiert, um keine unangemessenen Anforderungen an Bibliotheksimplementierer zu stellen. Wenn Sie versuchen, auf freigegebenen Speicher oder Speicher außerhalb eines zugewiesenen Blocks zuzugreifen, können verschiedene Dinge schief gehen:
Theorie der Arbeitsweise
Malloc (3) arbeitet also rückwärts von Ihrem Beispiel zur Gesamttheorie und erhält Speicher vom Kernel, wenn er benötigt wird, und normalerweise in Seiteneinheiten. Diese Seiten werden je nach Programm unterteilt oder konsolidiert. Malloc und Free arbeiten zusammen, um ein Verzeichnis zu führen. Sie verschmelzen nach Möglichkeit benachbarte freie Blöcke, um große Blöcke bereitstellen zu können. Das Verzeichnis kann die Verwendung des Speichers in freigegebenen Blöcken umfassen, um eine verknüpfte Liste zu bilden. (Die Alternative ist etwas gemeinsam genutzter Speicher und Paging-freundlicher und beinhaltet die Zuweisung von Speicher speziell für das Verzeichnis.) Malloc und Free können den Zugriff auf einzelne Blöcke kaum erzwingen, selbst wenn spezieller und optionaler Debugging-Code kompiliert wird das Programm.
1. Die Tatsache, dass nur sehr wenige Implementierungen von free () versuchen, Speicher an das System zurückzugeben, ist nicht unbedingt darauf zurückzuführen, dass die Implementierer nachlassen. Die Interaktion mit dem Kernel ist viel langsamer als die einfache Ausführung von Bibliothekscode, und der Nutzen wäre gering. Die meisten Programme haben einen stationären oder zunehmenden Speicherbedarf, sodass die Zeit, die für die Analyse des Heapspeichers auf der Suche nach Mehrwegspeicher aufgewendet wird, vollständig verschwendet wird. Andere Gründe sind die Tatsache, dass die interne Fragmentierung die Existenz von seitenausgerichteten Blöcken unwahrscheinlich macht und dass die Rückgabe eines Blocks wahrscheinlich Blöcke zu beiden Seiten fragmentieren würde. Schließlich umgehen die wenigen Programme, die viel Speicher zurückgeben, wahrscheinlich malloc () und weisen ohnehin einfach Seiten zu und geben sie frei.
quelle
Theoretisch erhält malloc für diese Anwendung Speicher vom Betriebssystem. Da Sie jedoch möglicherweise nur 4 Bytes benötigen und das Betriebssystem in Seiten (häufig 4 KB) arbeiten muss, leistet malloc etwas mehr. Es nimmt eine Seite auf und fügt dort seine eigenen Informationen ein, damit es verfolgen kann, was Sie dieser Seite zugewiesen und von ihr befreit haben.
Wenn Sie beispielsweise 4 Bytes zuweisen, gibt Ihnen malloc einen Zeiger auf 4 Bytes. Was Sie möglicherweise nicht erkennen, ist, dass der Speicher 8-12 Bytes vor Ihren 4 Bytes von malloc verwendet wird, um eine Kette des gesamten von Ihnen zugewiesenen Speichers zu erstellen. Wenn Sie kostenlos anrufen, nimmt es Ihren Zeiger, sichert sich dort, wo sich seine Daten befinden, und bearbeitet diesen.
Wenn Sie Speicher freigeben, nimmt malloc diesen Speicherblock aus der Kette ... und gibt diesen Speicher möglicherweise an das Betriebssystem zurück oder nicht. Wenn dies der Fall ist, schlägt der Zugriff auf diesen Speicher wahrscheinlich fehl, da das Betriebssystem Ihnen die Berechtigungen für den Zugriff auf diesen Speicherort entzieht. Wenn malloc den Speicher behält (weil auf dieser Seite andere Dinge zugewiesen sind oder für eine Optimierung), funktioniert der Zugriff zufällig. Es ist immer noch falsch, aber es könnte funktionieren.
HAFTUNGSAUSSCHLUSS: Was ich beschrieben habe, ist eine übliche Implementierung von Malloc, aber keineswegs die einzig mögliche.
quelle
Ihre strcpy-Zeile versucht aufgrund des NUL-Terminators, 9 Bytes und nicht 8 Bytes zu speichern. Es ruft undefiniertes Verhalten auf.
Der Aufruf zum kostenlosen kann abstürzen oder nicht. Der Speicher "nach" den 4 Bytes Ihrer Zuordnung wird möglicherweise von Ihrer C- oder C ++ - Implementierung für etwas anderes verwendet. Wenn es für etwas anderes verwendet wird, führt das Kritzeln dazu, dass "etwas anderes" schief geht. Wenn es jedoch nicht für etwas anderes verwendet wird, kann es passieren, dass Sie damit durchkommen. "Weg damit" klingt vielleicht gut, ist aber tatsächlich schlecht, da es bedeutet, dass Ihr Code in Ordnung zu sein scheint, aber bei einem zukünftigen Lauf werden Sie möglicherweise nicht damit durchkommen.
Bei einem Speicherzuweiser im Debugging-Stil stellen Sie möglicherweise fest, dass dort ein spezieller Schutzwert geschrieben wurde und dass dieser Wert kostenlos überprüft wird und in Panik gerät, wenn er nicht gefunden wird.
Andernfalls stellen Sie möglicherweise fest, dass die nächsten 5 Bytes einen Teil eines Verbindungsknotens enthalten, der zu einem anderen Speicherblock gehört, der noch nicht zugewiesen wurde. Das Freigeben Ihres Blocks kann durchaus das Hinzufügen zu einer Liste verfügbarer Blöcke umfassen. Da Sie in den Listenknoten gekritzelt haben, kann diese Operation einen Zeiger mit einem ungültigen Wert dereferenzieren und einen Absturz verursachen.
Es hängt alles vom Speicherzuweiser ab - unterschiedliche Implementierungen verwenden unterschiedliche Mechanismen.
quelle
Wie malloc () und free () funktionieren, hängt von der verwendeten Laufzeitbibliothek ab. Im Allgemeinen weist malloc () dem Betriebssystem einen Heap (einen Speicherblock) zu. Jede Anforderung an malloc () weist dann einen kleinen Teil dieses Speichers zu und gibt einen Zeiger an den Aufrufer zurück. Die Speicherzuweisungsroutinen müssen einige zusätzliche Informationen über den zugewiesenen Speicherblock speichern, um den verwendeten und freien Speicher auf dem Heap verfolgen zu können. Diese Informationen werden häufig in wenigen Bytes unmittelbar vor dem von malloc () zurückgegebenen Zeiger gespeichert und können eine verknüpfte Liste von Speicherblöcken sein.
Wenn Sie über den von malloc () zugewiesenen Speicherblock hinaus schreiben, werden Sie höchstwahrscheinlich einige der Buchhaltungsinformationen des nächsten Blocks zerstören, der möglicherweise der verbleibende nicht verwendete Speicherblock ist.
Ein Ort, an dem Ihr Programm möglicherweise ebenfalls abstürzt, ist das Kopieren zu vieler Zeichen in den Puffer. Wenn sich die zusätzlichen Zeichen außerhalb des Heaps befinden, kann es zu einer Zugriffsverletzung kommen, wenn Sie versuchen, in nicht vorhandenen Speicher zu schreiben.
quelle
Dies hat nichts spezielles mit malloc und free zu tun. Ihr Programm zeigt nach dem Kopieren der Zeichenfolge ein undefiniertes Verhalten - es kann zu diesem Zeitpunkt oder zu einem beliebigen späteren Zeitpunkt abstürzen. Dies gilt auch dann, wenn Sie nie malloc und free verwendet und das char-Array auf dem Stapel oder statisch zugewiesen haben.
quelle
malloc und free sind implementierungsabhängig. Eine typische Implementierung besteht darin, den verfügbaren Speicher in eine "freie Liste" zu partitionieren - eine verknüpfte Liste der verfügbaren Speicherblöcke. Viele Implementierungen unterteilen es künstlich in kleine und große Objekte. Freie Blöcke beginnen mit Informationen darüber, wie groß der Speicherblock ist und wo der nächste ist usw.
Wenn Sie malloc, wird ein Block von der freien Liste gezogen. Wenn Sie freigeben, wird der Block wieder in die kostenlose Liste aufgenommen. Wenn Sie das Ende Ihres Zeigers überschreiben, schreiben Sie wahrscheinlich in die Kopfzeile eines Blocks in der freien Liste. Wenn Sie Ihren Speicher freigeben, versucht free (), auf den nächsten Block zu schauen, und trifft wahrscheinlich auf einen Zeiger, der einen Busfehler verursacht.
quelle
Nun, es hängt von der Implementierung des Speicherzuweisers und dem Betriebssystem ab.
Unter Windows kann ein Prozess beispielsweise eine Seite oder mehr RAM anfordern. Das Betriebssystem weist diese Seiten dann dem Prozess zu. Dies ist jedoch nicht der Ihrer Anwendung zugewiesene Speicher. Der CRT-Speicherzuweiser markiert den Speicher als zusammenhängenden "verfügbaren" Block. Der CRT-Speicherzuweiser durchläuft dann die Liste der freien Blöcke und findet den kleinstmöglichen Block, den er verwenden kann. Es nimmt dann so viel von diesem Block, wie es benötigt, und fügt ihn einer "zugewiesenen" Liste hinzu. Am Kopf der eigentlichen Speicherzuordnung ist ein Header angebracht. Dieser Header enthält verschiedene Informationen (er kann beispielsweise die nächsten und vorherigen zugewiesenen Blöcke enthalten, um eine verknüpfte Liste zu bilden. Er enthält höchstwahrscheinlich die Größe der Zuordnung).
Free entfernt dann den Header und fügt ihn wieder der Liste des freien Speichers hinzu. Wenn es mit den umgebenden freien Blöcken einen größeren Block bildet, werden diese zu einem größeren Block addiert. Wenn jetzt eine ganze Seite frei ist, gibt der Allokator die Seite höchstwahrscheinlich an das Betriebssystem zurück.
Es ist kein einfaches Problem. Der OS-Allokator-Teil liegt völlig außerhalb Ihrer Kontrolle. Ich empfehle Ihnen, etwas wie Doug Leas Malloc (DLMalloc) durchzulesen, um zu verstehen, wie ein ziemlich schneller Allokator funktioniert.
Bearbeiten: Ihr Absturz wird durch die Tatsache verursacht, dass Sie durch Schreiben, das größer als die Zuordnung ist, den nächsten Speicherheader überschrieben haben. Auf diese Weise wird es beim Freigeben sehr verwirrt, was genau es freigibt und wie es in den folgenden Block übergeht. Dies kann nicht immer sofort zu einem Absturz führen. Dies kann später zu einem Absturz führen. Vermeiden Sie generell Speicherüberschreibungen!
quelle
Ihr Programm stürzt ab, weil es Speicher verwendet, der Ihnen nicht gehört. Es kann von jemand anderem verwendet werden oder nicht - wenn Sie Glück haben, stürzen Sie ab, wenn nicht, bleibt das Problem möglicherweise lange verborgen und kommt zurück und beißt Sie später.
Was die malloc / freie Implementierung angeht, sind ganze Bücher dem Thema gewidmet. Grundsätzlich würde der Allokator größere Speicherblöcke vom Betriebssystem erhalten und diese für Sie verwalten. Einige der Probleme, die ein Allokator angehen muss, sind:
quelle
Es ist schwer zu sagen, da das tatsächliche Verhalten zwischen verschiedenen Compilern / Laufzeiten unterschiedlich ist. Sogar Debug- / Release-Builds verhalten sich anders. Debug-Builds von VS2005 fügen Markierungen zwischen Zuweisungen ein, um eine Speicherbeschädigung zu erkennen. Anstelle eines Absturzes wird dies in free () bestätigt.
quelle
Es ist auch wichtig zu wissen, dass durch einfaches Bewegen des Programmunterbrechungszeigers mit
brk
undsbrk
ohne Zuweisen des Speichers nur der Adressraum eingerichtet wird. Unter Linux wird der Speicher beispielsweise durch tatsächliche physische Seiten "gesichert", wenn auf diesen Adressbereich zugegriffen wird, was zu einem Seitenfehler führt und schließlich dazu führt, dass der Kernel den Seitenzuweiser aufruft, um eine Sicherungsseite zu erhalten.quelle