Ein C ++ - Buch, das ich gelesen habe, besagt, dass beim Löschen eines Zeigers mit dem delete
Operator der Speicher an der Stelle, auf die er zeigt, "freigegeben" wird und überschrieben werden kann. Außerdem wird angegeben, dass der Zeiger weiterhin auf dieselbe Position zeigt, bis er neu zugewiesen oder auf gesetzt wird NULL
.
In Visual Studio 2012 jedoch; das scheint nicht der Fall zu sein!
Beispiel:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new int;
cout << "ptr = " << ptr << endl;
delete ptr;
cout << "ptr = " << ptr << endl;
system("pause");
return 0;
}
Wenn ich dieses Programm kompiliere und ausführe, erhalte ich die folgende Ausgabe:
ptr = 0050BC10
ptr = 00008123
Press any key to continue....
Die Adresse, auf die der Zeiger zeigt, ändert sich eindeutig, wenn delete aufgerufen wird!
Warum passiert dies? Hat dies speziell mit Visual Studio zu tun?
Und wenn Löschen die Adresse ändern kann, auf die es zeigt, warum sollte Löschen dann nicht automatisch den Zeiger auf NULL
anstelle einer zufälligen Adresse setzen?
quelle
Antworten:
Mir ist aufgefallen, dass die in gespeicherte Adresse
ptr
immer überschrieben wurde mit00008123
...Das schien seltsam, also habe ich ein wenig gegraben und diesen Microsoft-Blog-Beitrag gefunden , der einen Abschnitt über "Automatisierte Zeigerbereinigung beim Löschen von C ++ - Objekten" enthält.
Es erklärt nicht nur, was Visual Studio mit dem Zeiger nach dem Löschen macht, sondern auch, warum sie ihn NICHT
NULL
automatisch eingestellt haben!Diese "Funktion" wird im Rahmen der Einstellung "SDL-Prüfungen" aktiviert. Zum Aktivieren / Deaktivieren gehen Sie zu: PROJEKT -> Eigenschaften -> Konfigurationseigenschaften -> C / C ++ -> Allgemein -> SDL-Prüfungen
Um dies zu bestätigen:
Wenn Sie diese Einstellung ändern und denselben Code erneut ausführen, wird die folgende Ausgabe ausgegeben:
"feature" steht in Anführungszeichen, da in einem Fall, in dem Sie zwei Zeiger auf denselben Speicherort haben, durch Aufrufen von delete nur EINER von ihnen bereinigt wird . Der andere zeigt auf den ungültigen Ort ...
AKTUALISIEREN:
Nach weiteren 5 Jahren Erfahrung in der C ++ - Programmierung ist mir klar, dass dieses gesamte Problem im Grunde genommen ein strittiger Punkt ist. Wenn Sie ein C ++ - Programmierer sind und weiterhin Rohzeiger verwenden
new
unddelete
verwalten, anstatt intelligente Zeiger zu verwenden (die dieses gesamte Problem umgehen), sollten Sie eine Änderung des Karrierewegs in Betracht ziehen, um ein C-Programmierer zu werden. ;)quelle
0x8123
stattdessen verwenden0
. Der Zeiger ist immer noch ungültig, verursacht jedoch eine Ausnahme, wenn versucht wird, ihn zu dereferenzieren (gut), und er besteht keine NULL-Prüfungen (auch gut, da es ein Fehler ist, dies nicht zu tun). Wo ist der Ort für schlechte Gewohnheiten? Es ist wirklich nur etwas, das Ihnen beim Debuggen hilft.Sie sehen die Nebenwirkungen der
/sdl
Kompilierungsoption. Standardmäßig für VS2015-Projekte aktiviert, werden zusätzliche Sicherheitsüberprüfungen aktiviert, die über die von / gs bereitgestellten hinausgehen. Verwenden Sie Projekt> Eigenschaften> C / C ++> Allgemein> SDL prüft die Einstellung, um sie zu ändern.Zitat aus dem MSDN-Artikel :
Beachten Sie, dass das Setzen gelöschter Zeiger auf NULL eine schlechte Vorgehensweise ist, wenn Sie MSVC verwenden. Es verhindert die Hilfe, die Sie sowohl vom Debug-Heap als auch von dieser / sdl-Option erhalten. Sie können keine ungültigen Frei- / Löschaufrufe mehr in Ihrem Programm erkennen.
quelle
delete
einen haben, belässt Visual Studio den zweiten Zeiger auf seinen ursprünglichen Speicherort, der jetzt ungültig ist.Das sind definitiv irreführende Informationen.
Dies liegt eindeutig innerhalb der Sprachspezifikationen.
ptr
ist nach dem Aufruf von nicht gültigdelete
. Die Verwendungptr
nachdelete
d ist Ursache für undefiniertes Verhalten. Tu es nicht. Die Laufzeitumgebung kannptr
nach dem Aufruf von frei tun, was sie willdelete
.Das Ändern des Werts des Zeigers auf einen alten Wert liegt innerhalb der Sprachspezifikation. Was die Änderung in NULL angeht, würde ich sagen, das wäre schlecht. Das Programm würde sich vernünftiger verhalten, wenn der Wert des Zeigers auf NULL gesetzt würde. Dadurch wird das Problem jedoch ausgeblendet. Wenn das Programm mit unterschiedlichen Optimierungseinstellungen kompiliert oder in eine andere Umgebung portiert wird, tritt das Problem wahrscheinlich im ungünstigsten Moment auf.
quelle
delete
zweimaliger Aufruf des Zeigers würde kein Problem verursachen. Das ist definitiv nicht gut.NULL
aber definitiv außerhalb des Adressraums des Prozesses liegt, werden mehr Fälle als die beiden Alternativen angezeigt . Wenn Sie es baumeln lassen, wird dies nicht unbedingt zu einem Segfault führen, wenn es nach der Freigabe verwendet wird.NULL
Wenn Sie es so einstellen, wird kein Segfault verursacht, wenn esdelete
erneut angezeigt wird.Im Allgemeinen ist sogar das Lesen (wie oben beschrieben, beachten Sie: Dies unterscheidet sich von der Dereferenzierung) von Werten ungültiger Zeiger (Zeiger werden beispielsweise ungültig, wenn Sie
delete
dies tun ) ein implementierungsdefiniertes Verhalten. Dies wurde in CWG # 1438 eingeführt . Siehe auch hier .Bitte beachten Sie, dass das Lesen von Werten ungültiger Zeiger zuvor ein undefiniertes Verhalten war. Was Sie also oben haben, wäre ein undefiniertes Verhalten, was bedeutet, dass alles passieren kann.
quelle
[basic.stc.dynamic.deallocation]
: "Wenn das Argument für eine Freigabefunktion in der Standardbibliothek ein Zeiger ist, der nicht der Nullzeigerwert ist, muss die Freigabefunktion den vom Zeiger referenzierten Speicher freigeben, wodurch alle Zeiger ungültig werden, die auf einen verweisen." Teil des freigegebenen Speichers "und die Regel in[conv.lval]
(Abschnitt 4.1), die besagt, dass das Lesen (lWert-> rWert-Konvertierung) eines ungültigen Zeigerwerts ein implementierungsdefiniertes Verhalten ist.Ich glaube, Sie führen eine Art Debug-Modus aus und VS versucht, Ihren Zeiger auf einen bekannten Ort zu verschieben, damit ein weiterer Versuch, ihn zu dereferenzieren, verfolgt und gemeldet werden kann. Versuchen Sie, dasselbe Programm im Release-Modus zu kompilieren / auszuführen.
Zeiger werden im Inneren normalerweise nicht geändert,
delete
um die Effizienz zu erhöhen und um eine falsche Vorstellung von Sicherheit zu vermeiden. Das Setzen des Löschzeigers auf einen vordefinierten Wert ist in den meisten komplexen Szenarien nicht hilfreich, da der zu löschende Zeiger wahrscheinlich nur einer von mehreren Zeigern ist, die auf diese Position zeigen.Je mehr ich darüber nachdenke, desto mehr stelle ich fest, dass VS wie üblich daran schuld ist. Was ist, wenn der Zeiger const ist? Wird es das noch ändern?
quelle
Nach dem Löschen des Zeigers ist der Speicher, auf den er zeigt, möglicherweise noch gültig. Um diesen Fehler zu manifestieren, wird der Zeigerwert auf einen offensichtlichen Wert gesetzt. Dies hilft wirklich beim Debuggen. Wenn der Wert auf gesetzt wurde,
NULL
wird er möglicherweise nie als potenzieller Fehler im Programmablauf angezeigt. So kann es einen Fehler verbergen, wenn Sie später gegen testenNULL
.Ein weiterer Punkt ist, dass einige Laufzeitoptimierer diesen Wert überprüfen und seine Ergebnisse ändern können.
In früheren Zeiten stellte MS den Wert auf ein
0xcfffffff
.quelle