Was bestimmt, was in einen C ++ - Zeiger geschrieben wird, wenn delete aufgerufen wird?

8

Ich habe einen Zeiger auf eine bestimmte Klasse. Nehmen wir zum Beispiel an, der Zeiger ist:

%Vor%

Dieser Zeiger zeigt auf:

%Vor%

Welches ist die virtuelle Funktionstabelle meiner Klasse? Das macht Sinn für mich. Im windgg sieht alles gut aus.

Ich lösche den Zeiger. Jetzt bei 0x24083094 ist:

%Vor%

Aber es ist nicht irgendein zufälliger Müll, diese Adresse wird jedes Mal dort eingefügt, es ist konsistent 0x604751f8 ! So sehr, dass ich diese Adresse tatsächlich verwenden kann, um festzustellen, ob dieser Zeiger zwischen den Ausführungen meiner Anwendung gelöscht wurde!

Aber warum? Wie bestimmt es, dass 0x604751f8 dort geschrieben werden sollte?

Für die Aufnahme verwende ich Windows, die unter Visual Studio 2003 gebaut werden.

Ich weiß, dass ich mich nicht darauf verlassen kann, dass dieser Wert festgelegt wird, auch wenn es scheint, dass es konsistent ist, aber kann ich mich darauf verlassen, dass es anders ist? Das heißt, 0x03ac9184 ist nicht 0x24083094 , wenn der Zeiger gelöscht wird, oder? Was wird dort hingelegt? Es könnte alles sein, aber 0x03ac9184 wird definitiv nicht da sein (sonst könnte ich noch Methoden aufrufen, da das die virtuelle Funktionstabelle ist). Habe ich Recht?

Ich fühle mich als hätte ich eine Antwort. kann sich nach dem Löschen nicht auf etwas verlassen. Vielleicht hilft ein Hintergrund den Leuten zu sehen, woher ich komme. Im Wesentlichen versuche ich einen Fehler zu beheben, bei dem ein Zeiger unter mir gelöscht wird. Es ist eine lange Geschichte, ich werde nicht auf die Details eingehen.

Grundsätzlich versuche ich zu erkennen, dass ich mich in dieser Situation befinde, damit ich mich elegant von meiner Funktion entfernen kann. Ich nehme an, der einfachste und beste Weg ist, herauszufinden, wem dieser Zeiger gehört, und ihn zu fragen, ob sich etwas geändert hat. Also werde ich eine solche Lösung implementieren. Es vermeidet jede dieser C ++ löschen Hacker-y ich diskutierte.

Jedoch ist das Interessante daran, dass wir in unserem Code eine Klasse namens "BogusObject" haben, die im Wesentlichen wie ein Tablett wirkt, das Leute fängt, die versehentlich befreite Objekte dereferenzieren. Im Grunde haken wir unsere eigenen Löschfunktionen ein und binden die BogusObject-Klasse in die vtable einer beliebigen freigegebenen Klasse.

Dann, wenn jemand etwas anruft, bekommen sie eine nette Nachricht, die etwas mit dem Effekt "Hey, etwas ist falsch, Alter" sagt. Das passiert in meinem Fall. Das heißt, 0x604751f8+(someoffset) befindet sich in der BogusObject-Klasse. Aber wir verwenden BogusObject nicht mehr! Es ist buchstäblich nirgendwo eingerichtet (sogar wenn die BogusObject-Klasse vollständig entfernt wird), und trotzdem bekomme ich die nette Nachricht, dass etwas nicht stimmt! Aber ich bin jetzt der Meinung, dass es ein Zufall ist.

Aus irgendeinem Grund schreibt die Laufzeit den Wert 0x604751f8 in diesen Zeiger, wenn er gelöscht wird, und das entspricht zufällig der einen Klasse, die einen Zweck hat, Situationen wie diese zu erfassen!

    
pj4533 07.05.2009, 17:12
quelle

10 Antworten

16

Nichts im Standard bestimmt, was dort geschrieben wird. Visual Studio (zumindest im Debug-Modus) wird oft überall semantische Werte schreiben, um Fehler frühzeitig zu erkennen.

Auf diesen Wert kann man sich nicht verlassen, aber wenn Sie jemals herausfinden, dass dieser Wert auf geheimnisvolle Weise in Ihrem Programm auftaucht, können Sie davon ausgehen, dass Sie irgendwo auf gelöschten Speicher verweisen. Siehe diese Antwort für eine Liste von Werten unter einem Compiler.

Es ist auch durchaus möglich, dass es ein freier Listenzeiger ist, der auf den nächsten freien Speicher zeigt. Die meisten Speicherzuordner speichern ihren freien Speicher in einer verknüpften Liste von Arten und verwenden den freien Speicher, den sie verfolgen, um die Überwachungsdaten zu speichern.

In keinem Fall dürfen Sie diesen Zeigerwert für irgendetwas verwenden, das Sie weiter verwenden möchten, es sei denn, Sie rufen Microsoft auf und erhalten eine Dokumentation, in der Sie erklären, warum dieser Wert so ist, und garantieren, dass er sich nicht ändert . Und selbst dann wissen Sie, dass Ihr Code jetzt an das Verhalten eines Compilers gebunden ist. In C ++ ist der Zugriff auf nicht zugeordneten Speicher nicht definiert und böse.

Bearbeiten: Sie können sich nicht einmal darauf verlassen, dass sich der Wert nach einem Löschvorgang ändert. Es gibt nichts, was ein Compiler benötigt, um die Daten beim Löschen zu ändern.

    
Eclipse 07.05.2009, 17:14
quelle
4

Wenn Sie ein Objekt löschen, wird der Speicher, den er verwendet hat, in den freien Speicher (Heap) zurückgesetzt. Natürlich hat der freie Speicher seine eigenen Datenstrukturen (und möglicherweise Debugging-Daten), die er auf diesen Speicher anwenden wird.

Was bedeutet der bestimmte Wert, den Sie betrachten? Könnte fast alles sein.

    
Michael Burr 07.05.2009 17:23
quelle
2

Das Aufrufen des Löschoperators für einen Zeiger bedeutet nicht, dass der gelöschte Speicher gelöscht wird. Es ruft nur den Destruktor des gelöschten Objekts (der gelöschten Objekte) auf und markiert den zugewiesenen Heap-Speicher als freigegeben. (Dies ist das Standardverhalten des Löschoperators.)

Wenn Sie den Speicherinhalt beim Löschen löschen müssen, müssen Sie den Löschoperator außer Kraft setzen.

    
Cătălin Pitiș 07.05.2009 17:16
quelle
2

Wie Josh sagte, gibt es eine Reihe von Werten, die eingefügt werden können, um dem Debugger das Leben mit Debug-Builds zu erleichtern. Diese sind compilerspezifisch und in keiner Weise verlässlich. In einem Release-Build glaube ich, dass das Standardverhalten der meisten C ++ - Compiler nichts mit dem freigegebenen Speicher zu tun hat. Daher wird der Inhalt, bis dieser Adressraum wieder zugewiesen wird, im Grunde genommen das sein, was Sie jemals zuvor hatten sollte sich NIEMALS darauf verlassen.

    
Serapth 07.05.2009 17:24
quelle
2

Es gibt mit ziemlicher Sicherheit eine spezifische interne Bedeutung für den Wert, der bei jedem Löschen des Objekts angezeigt wird.

Es kann sich jedoch mit der nächsten Version von Visual C ++ ändern und wird auf anderen Compiler anderer Anbieter sicherlich anders sein.

Die Tatsache, dass es jedes Mal, wenn Sie ein Objekt löschen, gleich aussieht, bedeutet nichts Nützliches . Es kann auch nicht potenziell nützlich sein. Angenommen, Sie würden einen Weg finden, davon Gebrauch zu machen, wäre es ein entsetzlicher Hack, und Sie würden es irgendwann bereuen.

Versuchen Sie, es aus Ihren Gedanken zu verdrängen!

    
Daniel Earwicker 07.05.2009 17:46
quelle
2

Wie Michael Burr schon sagte, geht die Erinnerung an den freien Laden zurück. Einige freie Speicher werden als verknüpfte Listen implementiert, wobei der - & gt; next-Zeiger an den Anfang des freien Puffers gesetzt wird. Es ist möglich, dass die magische Zahl, die Sie sehen (0x604751f8), das Ende der Liste ist. Sie können dies überprüfen, indem Sie das folgende Experiment durchführen:

%Vor%

Lass uns wissen, was du findest!

    
florin 07.05.2009 17:47
quelle
2

Wahrscheinlich ist dieser Zeigerwert die vtable der Basisklasse. Wenn ein Destruktor für eine abgeleitete Klasse ausgeführt wird, wird der Speicher nach dem Abschluss des normalen Hauptteils als Basistyp neu formatiert (im Grunde wird der Vtable-Zeiger der Basisklasse in das Objekt geschrieben) und anschließend der Basisklassen-Destruktor aufgerufen.

Beachten Sie, dass dieses Verhalten ein internes Implementierungsdetail der Laufzeit-C ++ - Unterstützung des Compilers ist, sodass andere Compiler (oder zukünftige Versionen desselben Compilers) etwas völlig anderes tun können. Aber diese 'vtable in die Basisklasse konvertieren und den Basisklassendestruktor aufrufen' ist relativ verbreitet und stammt aus der ursprünglichen cfront-Implementierung von C ++

    
Chris Dodd 07.05.2009 18:05
quelle
2

Nein, Sie können sich nicht darauf verlassen, dass es eingestellt wird. Sie können sich nicht einmal darauf verlassen, dass es anders ist.

MS-DOS-Heap-Manager erlaubten häufig, den freigegebenen Speicher bis zum nächsten Aufruf von malloc zu verwenden. Neu und löschen in diesem Zeitalter namens malloc und kostenlos.

Die meisten Heap-Manager sind heutzutage vernünftig, wenn sie Speicher an das Betriebssystem zurückgeben, was bedeutet, dass Sie sich nicht einmal darauf verlassen können, dass es lesbar ist! Sogar diejenigen, die es noch erlauben (glibc hat einen bwd-compat-Modus, der es erlaubt), unterliegen Thread-Race-Bedingungen.

Außerdem darf delete den Zeiger auf NULL setzen, wenn es ein lvalue ist.

Wenn Sie delete aufrufen, denken Sie nicht einmal mehr daran, den Zeiger zu dereferenzieren.

    
Joshua 07.05.2009 18:13
quelle
2

Das Programm versucht Ihnen etwas zu sagen. Ein Datum, eine Telefonnummer, wer weiß?

Nun, im Ernst, das ist nicht spezifiziert , total implementierungsabhängig , und natürlich versucht dieser Zeiger nach delete zu dereferenzieren würde zu undefiniertem Verhalten führen. Kurz gesagt, Wen interessiert das?

    
Daniel Daranas 07.05.2009 18:11
quelle
0

Sie haben Zugriff auf den CRT-Quellcode in Visual Studio. Du könntest einen Blick darauf werfen. Ich habe einmal einen Bug, den ich hatte, besser verstanden.

    
Gigi 13.05.2009 21:55
quelle