Ich beginne mit den Worten: Verwenden Sie intelligente Zeiger, und Sie müssen sich darüber keine Sorgen machen.
Was sind die Probleme mit dem folgenden Code?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Dies wurde durch eine Antwort und Kommentare zu einer anderen Frage ausgelöst . Ein Kommentar von Neil Butterworth sorgte für einige positive Stimmen:
Das Setzen von Zeigern auf NULL nach dem Löschen ist in C ++ keine universelle bewährte Methode. Es gibt Zeiten, in denen es gut ist, und Zeiten, in denen es sinnlos ist und Fehler verbergen kann.
Es gibt viele Umstände, unter denen es nicht helfen würde. Aber meiner Erfahrung nach kann es nicht schaden. Jemand hat mich aufgeklärt.
c++
pointers
null
dynamic-allocation
Mark Ransom
quelle
quelle
delete
einen Nullzeiger, was ein Grund dafür ist, dass das Nullstellen eines Zeigers gut sein kann.Antworten:
Durch Setzen eines Zeigers auf 0 (was in Standard-C ++ "null" ist, die NULL-Definition von C ist etwas anders) werden Abstürze bei doppelten Löschvorgängen vermieden.
Folgendes berücksichtigen:
Wohingegen:
Mit anderen Worten, wenn Sie gelöschte Zeiger nicht auf 0 setzen, treten Probleme auf, wenn Sie doppelt löschen. Ein Argument gegen das Setzen von Zeigern auf 0 nach dem Löschen wäre, dass dadurch nur doppelte Löschfehler maskiert und unbehandelt bleiben.
Es ist natürlich am besten, keine doppelten Löschfehler zu haben, aber abhängig von der Besitzersemantik und den Objektlebenszyklen kann dies in der Praxis schwierig zu erreichen sein. Ich bevorzuge einen maskierten doppelten Löschfehler gegenüber UB.
Als Nebenbemerkung zum Verwalten der Objektzuweisung schlage ich vor, dass Sie sich je nach Ihren Anforderungen einen Blick auf
std::unique_ptr
strikte / singuläre Eigentumsverhältnisse,std::shared_ptr
auf gemeinsame Eigentumsverhältnisse oder eine andere Implementierung eines intelligenten Zeigers werfen .quelle
NULL
einen doppelten Löschfehler maskieren kann. (Einige mögen diese Maske tatsächlich als Lösung betrachten - sie ist es, aber nicht sehr gut, da sie dem Problem nicht auf den Grund geht.) Wenn Sie sie jedoch nicht (weit!) Mehr auf NULL-Masken setzen Häufige Probleme beim Zugriff auf die Daten nach dem Löschen.unique_ptr
dieauto_ptr
Bewegungssemantik ersetzt , die das tut, was versucht wurde.Das Setzen von Zeigern auf NULL, nachdem Sie das gelöscht haben, worauf es hingewiesen hat, kann sicherlich nicht schaden, aber es ist oft eine Art Pflaster für ein grundlegenderes Problem: Warum verwenden Sie überhaupt einen Zeiger? Ich kann zwei typische Gründe sehen:
std::vector
funktioniert es und es löst das Problem, versehentlich Zeiger auf freigegebenen Speicher zu belassen. Es gibt keine Zeiger.new
möglicherweise nicht derselbe wie derdelete
aufgerufene. In der Zwischenzeit haben möglicherweise mehrere Objekte das Objekt gleichzeitig verwendet. In diesem Fall wäre ein gemeinsamer Zeiger oder ähnliches vorzuziehen gewesen.Meine Faustregel lautet: Wenn Sie Zeiger im Benutzercode belassen, machen Sie es falsch. Der Zeiger sollte überhaupt nicht da sein, um auf Müll zu zeigen. Warum übernimmt kein Objekt die Verantwortung für die Sicherstellung seiner Gültigkeit? Warum endet sein Gültigkeitsbereich nicht, wenn das Objekt, auf das verwiesen wird, dies tut?
quelle
new
.Ich habe eine noch bessere Best Practice: Wenn möglich, beenden Sie den Gültigkeitsbereich der Variablen!
quelle
Ich setze immer einen Zeiger auf
NULL
(jetztnullptr
), nachdem ich die Objekte gelöscht habe, auf die es zeigt.Es kann dabei helfen, viele Verweise auf freigegebenen Speicher abzufangen (vorausgesetzt, Ihre Plattform weist Fehler bei einem Deref eines Nullzeigers auf).
Es werden nicht alle Verweise auf den freien Speicher abgefangen, wenn beispielsweise Kopien des Zeigers herumliegen. Aber manche sind besser als keine.
Es wird ein doppeltes Löschen maskieren, aber ich finde, dass diese weitaus seltener sind als Zugriffe auf bereits freigegebenen Speicher.
In vielen Fällen wird der Compiler es weg optimieren. Das Argument, dass es unnötig ist, überzeugt mich also nicht.
Wenn Sie bereits RAII verwenden, enthält
delete
Ihr Code zunächst nicht viele s, sodass mich das Argument, dass die zusätzliche Zuweisung Unordnung verursacht, nicht überzeugt.Beim Debuggen ist es oft praktisch, den Nullwert anstelle eines veralteten Zeigers zu sehen.
Wenn Sie dies immer noch stört, verwenden Sie stattdessen einen intelligenten Zeiger oder eine Referenz.
Ich habe auch andere Arten von Ressourcenhandles auf den Wert ohne Ressource gesetzt, wenn die Ressource frei ist (normalerweise nur im Destruktor eines RAII-Wrappers, der zur Kapselung der Ressource geschrieben wurde).
Ich habe an einem großen (9 Millionen Aussagen) kommerziellen Produkt gearbeitet (hauptsächlich in C). An einem Punkt haben wir Makromagie verwendet, um den Zeiger auf Null zu setzen, wenn Speicher freigegeben wurde. Dies enthüllte sofort viele lauernde Fehler, die sofort behoben wurden. Soweit ich mich erinnern kann, hatten wir nie einen doppelten Fehler.
Update: Microsoft ist der Ansicht, dass dies eine bewährte Methode für die Sicherheit ist, und empfiehlt diese Vorgehensweise in den SDL-Richtlinien. Anscheinend stampft MSVC ++ 11 den gelöschten Zeiger automatisch (unter vielen Umständen), wenn Sie mit der Option / SDL kompilieren.
quelle
Erstens gibt es viele Fragen zu diesem und eng verwandten Themen, z. B. Warum nicht löschen, setzen Sie den Zeiger auf NULL? .
In Ihrem Code das Problem, was in (verwenden Sie p) vor sich geht. Zum Beispiel, wenn Sie irgendwo Code wie diesen haben:
Wenn Sie dann p auf NULL setzen, wird nur sehr wenig erreicht, da Sie immer noch den Zeiger p2 haben, über den Sie sich Sorgen machen müssen.
Dies bedeutet nicht, dass das Setzen eines Zeigers auf NULL immer sinnlos ist. Wenn p beispielsweise eine Mitgliedsvariable ist, die auf eine Ressource verweist, deren Lebensdauer nicht genau mit der Klasse übereinstimmt, die p enthält, kann das Setzen von p auf NULL eine nützliche Methode sein, um das Vorhandensein oder Fehlen der Ressource anzuzeigen.
quelle
Wenn nach dem Code mehr Code steht
delete
, Ja. Wenn der Zeiger in einem Konstruktor oder am Ende einer Methode oder Funktion gelöscht wird, Nr.In dieser Parabel soll der Programmierer zur Laufzeit daran erinnert werden, dass das Objekt bereits gelöscht wurde.
Eine noch bessere Vorgehensweise ist die Verwendung von Smart Pointers (gemeinsam genutzt oder mit Gültigkeitsbereich), die ihre Zielobjekte automatisch löschen.
quelle
Wie andere gesagt haben,
delete ptr; ptr = 0;
wird es nicht dazu führen, dass Dämonen aus deiner Nase fliegen. Es fördert jedoch die Verwendungptr
als eine Art Flagge. Der Code wird übersätdelete
und setzt den Zeiger aufNULL
. Der nächste Schritt besteht darin,if (arg == NULL) return;
Ihren Code zu verteilen, um sich vor der versehentlichen Verwendung einesNULL
Zeigers zu schützen . Das Problem tritt auf, sobald die Überprüfungen gegen SieNULL
zu Ihrem primären Mittel zur Überprüfung des Status eines Objekts oder Programms werden.Ich bin mir sicher, dass es einen Codegeruch gibt, irgendwo einen Zeiger als Flag zu verwenden, aber ich habe keinen gefunden.
quelle
NULL
kein gültiger Wert ist, sollten Sie stattdessen wahrscheinlich eine Referenz verwenden.Ich werde Ihre Frage leicht ändern:
Es gibt zwei Szenarien, in denen das Setzen des Zeigers auf NULL übersprungen werden kann:
In der Zwischenzeit klingt das Argumentieren, dass das Setzen des Zeigers auf NULL Fehler für mich verbergen könnte, wie das Argumentieren, dass Sie einen Fehler nicht beheben sollten, da durch den Fix möglicherweise ein anderer Fehler ausgeblendet wird. Die einzigen Fehler, die auftreten können, wenn der Zeiger nicht auf NULL gesetzt ist, sind diejenigen, die versuchen, den Zeiger zu verwenden. Aber das Setzen auf NULL würde tatsächlich genau den gleichen Fehler verursachen, der sich zeigen würde, wenn Sie es mit freigegebenem Speicher verwenden, nicht wahr?
quelle
Wenn Sie keine andere Einschränkung haben, die Sie zwingt, den Zeiger nach dem Löschen auf NULL zu setzen oder nicht zu setzen (eine solche Einschränkung wurde von Neil Butterworth erwähnt ), dann ist es meine persönliche Präferenz, ihn zu belassen.
Für mich lautet die Frage nicht "Ist das eine gute Idee?" aber "Welches Verhalten würde ich verhindern oder zulassen, um dies zu erreichen?" Wenn auf diese Weise beispielsweise anderer Code erkennen kann, dass der Zeiger nicht mehr verfügbar ist, warum versucht dann anderer Code überhaupt, freigegebene Zeiger zu betrachten, nachdem sie freigegeben wurden? Normalerweise ist es ein Fehler.
Es erledigt auch mehr Arbeit als nötig und behindert das Post-Mortem-Debugging. Je weniger Sie den Speicher berühren, nachdem Sie ihn nicht benötigt haben, desto einfacher ist es herauszufinden, warum etwas abgestürzt ist. Ich habe mich oft darauf verlassen, dass sich der Speicher in einem ähnlichen Zustand befindet wie zu dem Zeitpunkt, als ein bestimmter Fehler aufgetreten ist, um diesen Fehler zu diagnostizieren und zu beheben.
quelle
Das explizite Nullstellen nach dem Löschen legt dem Leser stark nahe, dass der Zeiger etwas darstellt, das konzeptionell optional ist . Wenn ich das sehen würde, würde ich mir Sorgen machen, dass überall in der Quelle der Zeiger verwendet wird, der zuerst gegen NULL getestet werden sollte.
Wenn Sie das tatsächlich meinen, ist es besser, dies in der Quelle mit etwas wie boost :: optional explizit zu machen
Aber wenn Sie wirklich wollten, dass die Leute wissen, dass der Zeiger "schlecht geworden" ist, stimme ich zu 100% mit denen überein, die sagen, dass es das Beste ist, ihn aus dem Rahmen zu werfen. Dann verwenden Sie den Compiler, um die Möglichkeit fehlerhafter Dereferenzen zur Laufzeit zu verhindern.
Das ist das Baby im ganzen C ++ Badewasser, sollte es nicht rauswerfen. :) :)
quelle
In einem gut strukturierten Programm mit entsprechender Fehlerprüfung, gibt es keinen Grund , nicht es null zuzuordnen.
0
steht in diesem Zusammenhang allein als allgemein anerkannter ungültiger Wert. Scheitern Sie hart und scheitern Sie bald.Viele der Argumente gegen die Zuweisung
0
legen nahe , dass es könnte einen Fehler oder komplizieren Steuerfluss verstecken. Grundsätzlich ist dies entweder ein Upstream-Fehler (nicht Ihre Schuld (Entschuldigung für das schlechte Wortspiel)) oder ein anderer Fehler im Namen des Programmierers - vielleicht sogar ein Hinweis darauf, dass der Programmfluss zu komplex geworden ist.Wenn der Programmierer die Verwendung eines Zeigers einführen möchte, der als spezieller Wert null sein kann, und alle erforderlichen Ausweichmanöver darauf schreiben möchte, ist dies eine Komplikation, die er absichtlich eingeführt hat. Je besser die Quarantäne ist, desto eher finden Sie Fälle von Missbrauch und desto weniger können sie sich auf andere Programme ausbreiten.
Gut strukturierte Programme können unter Verwendung von C ++ - Funktionen entworfen werden, um diese Fälle zu vermeiden. Sie können Referenzen verwenden oder einfach sagen, dass das Übergeben / Verwenden von null oder ungültigen Argumenten ein Fehler ist - ein Ansatz, der auch für Container wie Smart Pointer gilt. Das Erhöhen des konsistenten und korrekten Verhaltens verhindert, dass diese Fehler weit kommen.
Von dort aus haben Sie nur einen sehr begrenzten Bereich und Kontext, in dem möglicherweise ein Nullzeiger vorhanden ist (oder zulässig ist).
Gleiches kann auf Zeiger angewendet werden, die dies nicht sind
const
. Das Verfolgen des Werts eines Zeigers ist trivial, da sein Umfang so klein ist und die missbräuchliche Verwendung überprüft und genau definiert wird. Wenn Ihr Toolset und Ihre Techniker dem Programm nach einem schnellen Lesen nicht folgen können oder eine unangemessene Fehlerprüfung oder ein inkonsistenter / milder Programmfluss vorliegt, haben Sie andere, größere Probleme.Schließlich verfügt Ihr Compiler und Ihre Umgebung wahrscheinlich über einige Schutzfunktionen für die Zeiten, in denen Sie Fehler (Scribbling) einführen, Zugriffe auf freigegebenen Speicher erkennen und andere verwandte UB abfangen möchten. Sie können ähnliche Programme auch in Ihre Programme einführen, häufig ohne Auswirkungen auf vorhandene Programme.
quelle
Lassen Sie mich erweitern, was Sie bereits in Ihre Frage aufgenommen haben.
Folgendes haben Sie in Aufzählungszeichenform in Ihre Frage eingefügt:
Das Setzen von Zeigern auf NULL nach dem Löschen ist in C ++ keine universelle bewährte Methode. Es gibt Zeiten, in denen:
Es gibt jedoch keine Zeiten, in denen dies schlecht ist ! Sie werden keine weiteren Fehler einführen, indem Sie sie explizit auf Null setzen. Sie werden keinen Speicher verlieren . Sie werden kein undefiniertes Verhalten verursachen .
Also, wenn Sie Zweifel haben, machen Sie es einfach null.
Wenn Sie jedoch der Meinung sind, dass Sie einen Zeiger explizit auf Null setzen müssen, klingt dies für mich so, als hätten Sie eine Methode nicht ausreichend aufgeteilt und sollten sich den Refactoring-Ansatz mit dem Namen "Extract method" ansehen, in den die Methode aufgeteilt werden soll separate Teile.
quelle
Ja.
Der einzige "Schaden", den es anrichten kann, besteht darin, Ineffizienz (eine unnötige Speicheroperation) in Ihr Programm einzuführen. Dieser Overhead ist jedoch in den meisten Fällen im Verhältnis zu den Kosten für die Zuweisung und Freigabe des Speicherblocks unbedeutend.
Wenn Sie es nicht tun, Sie werden einige böse Zeiger derefernce Fehler eines Tages.
Ich benutze immer ein Makro zum Löschen:
(und ähnliches für ein Array, free (), Freigabe von Handles)
Sie können auch "Selbstlösch" -Methoden schreiben, die auf den Zeiger des aufrufenden Codes verweisen, sodass der Zeiger des aufrufenden Codes auf NULL gesetzt wird. So löschen Sie beispielsweise einen Teilbaum vieler Objekte:
bearbeiten
Ja, diese Techniken verstoßen gegen einige Regeln zur Verwendung von Makros (und ja, heutzutage könnten Sie wahrscheinlich das gleiche Ergebnis mit Vorlagen erzielen) - aber durch die Verwendung über viele Jahre habe ich nie auf toten Speicher zugegriffen - eine der schlimmsten und schwierigsten und Am zeitaufwändigsten ist das Debuggen von Problemen, mit denen Sie konfrontiert werden können. In der Praxis haben sie über viele Jahre hinweg eine ganze Klasse von Fehlern aus jedem Team, in dem ich sie vorgestellt habe, effektiv beseitigt.
Es gibt auch viele Möglichkeiten, wie Sie das oben Genannte implementieren können. Ich versuche nur, die Idee zu veranschaulichen, Menschen zu zwingen, einen Zeiger auf NULL zu setzen, wenn sie ein Objekt löschen, anstatt ihnen die Möglichkeit zu geben, den Speicher freizugeben, der den Zeiger des Aufrufers nicht auf Null setzt .
Das obige Beispiel ist natürlich nur ein Schritt in Richtung eines Auto-Zeigers. Was ich nicht vorgeschlagen habe, weil das OP speziell nach dem Fall gefragt hat, dass kein Auto-Zeiger verwendet wird.
quelle
anObject->Delete(anObject)
denanObject
Zeiger ungültig zu machen . Das ist nur erschreckend. Sie sollten hierfür eine statische Methode erstellen, damit SieTreeItem::Delete(anObject)
zumindest dazu gezwungen sind ."Es gibt Zeiten, in denen es gut ist, und Zeiten, in denen es sinnlos ist und Fehler verbergen kann."
Ich kann zwei Probleme sehen: Dieser einfache Code:
wird zu einem For-Liner in einer Multithread-Umgebung:
Die "Best Practice" von Don Neufeld gilt nicht immer. Zum Beispiel mussten wir in einem Automobilprojekt auch in Destruktoren Zeiger auf 0 setzen. Ich kann mir vorstellen, dass solche Regeln in sicherheitskritischer Software keine Seltenheit sind. Es ist einfacher (und klüger), ihnen zu folgen, als zu versuchen, das Team / die Codeprüfung für jeden im Code verwendeten Zeiger davon zu überzeugen, dass eine Zeile, die diesen Zeiger auf Null setzt, redundant ist.
Eine weitere Gefahr besteht darin, sich bei Ausnahmen mit Code auf diese Technik zu verlassen:
In einem solchen Code erzeugen Sie entweder ein Ressourcenleck und verschieben das Problem, oder der Prozess stürzt ab.
Diese beiden Probleme, die mir spontan durch den Kopf gehen (Herb Sutter würde sicherlich mehr erzählen), werfen für mich alle Fragen der Art "Wie vermeide ich die Verwendung von Smart-Pointern auf und erledige die Arbeit sicher mit normalen Zeigern" als veraltet auf.
quelle
Es gibt immer baumelnde Zeiger , über die man sich Sorgen machen muss.
quelle
Wenn Sie den Zeiger neu zuweisen möchten, bevor Sie ihn erneut verwenden (Dereferenzieren, Übergeben an eine Funktion usw.), ist es nur eine zusätzliche Operation, den Zeiger auf NULL zu setzen. Wenn Sie jedoch nicht sicher sind, ob es neu zugewiesen wird oder nicht, bevor es erneut verwendet wird, ist es eine gute Idee, es auf NULL zu setzen.
Wie viele gesagt haben, ist es natürlich viel einfacher, nur intelligente Zeiger zu verwenden.
Bearbeiten: Wie Thomas Matthews in dieser früheren Antwort sagte, muss beim Löschen eines Zeigers in einem Destruktor kein NULL zugewiesen werden, da er nicht erneut verwendet wird, da das Objekt bereits zerstört wird.
quelle
Ich kann mir vorstellen, einen Zeiger nach dem Löschen auf NULL zu setzen, was in seltenen Fällen nützlich ist, in denen es ein legitimes Szenario gibt, ihn in einer einzelnen Funktion (oder einem einzelnen Objekt) wiederzuverwenden. Andernfalls macht es keinen Sinn - ein Zeiger muss auf etwas Sinnvolles verweisen, solange es existiert - Punkt.
quelle
Wenn der Code nicht zum leistungskritischsten Teil Ihrer Anwendung gehört, halten Sie ihn einfach und verwenden Sie einen shared_ptr:
Es führt eine Referenzzählung durch und ist threadsicher. Sie finden es im tr1 (std :: tr1-Namespace, #include <speicher>) oder wenn Ihr Compiler es nicht bereitstellt, holen Sie es von boost.
quelle