Dies ist nur dann eine "tickende Bombe", wenn Sie den Wortlaut der Spezifikation pedantisch beurteilen. Unabhängig davon ist es jedoch ein schrecklicher, schlecht beratener Ansatz, da er einen Programmfehler verdeckt. Allein aus diesem Grund würde ich es entfernen , auch wenn es erhebliche Arbeit bedeutet. Es ist kein unmittelbares (oder sogar mittelfristiges) Risiko, aber es ist einfach kein guter Ansatz.
Ein solches Verhalten beim Ausblenden von Fehlern ist auch nichts, worauf Sie sich verlassen möchten. Stellen Sie sich vor, Sie verlassen sich auf dieses Verhalten (dh es spielt keine Rolle, ob meine Objekte gültig sind, es funktioniert trotzdem! ), Und dann optimiert der Compiler die Gefahr if
in einem bestimmten Fall, weil er beweisen kann, dass dies this
kein a ist Null Zeiger. Dies ist eine legitime Optimierung nicht nur für einen hypothetischen zukünftigen Compiler, sondern auch für sehr reale Compiler der Gegenwart.
Aber natürlich, da Ihr Programm nicht wohlgeformt, es geschieht an einem gewissen Punkt , dass Sie es ein Null passieren this
rund 20 Ecken. Bang, du bist tot.
Das ist zwar sehr verdorben, und es wird nicht passieren, aber Sie können nicht 100% sicher sein, dass es immer noch so istkann unmöglich passieren.
Beachten Sie, dass wenn ich "Entfernen!" Rufe, dies nicht bedeutet, dass die gesamte Menge sofort oder in einem massiven Arbeitskräftebetrieb entfernt werden muss. Sie können diese Überprüfungen einzeln entfernen, wenn Sie auf sie stoßen (wenn Sie ohnehin etwas in derselben Datei ändern, vermeiden Sie Neukompilierungen), oder einfach nach einer Textsuche suchen (vorzugsweise in einer häufig verwendeten Funktion) und diese entfernen.
Da Sie GCC verwenden, sind Sie möglicherweise interessiert __builtin_return_address
, was Ihnen dabei helfen kann, diese Überprüfungen ohne großen Personalaufwand zu entfernen, den gesamten Workflow vollständig zu stören und die Anwendung vollständig unbrauchbar zu machen. Ändern Sie die Prüfung
vor dem Entfernen so, dass sie die Adresse des Anrufers ausgibt, und addr2line
geben Sie den Speicherort in Ihrer Quelle an. Auf diese Weise sollten Sie in der Lage sein, schnell alle Speicherorte in der Anwendung zu identifizieren, die sich falsch verhalten, damit Sie diese beheben können.
Also statt
if(!this) return 0;
Ändern Sie jeweils einen Ort in:
if(!this) { __builtin_printf("!!! %p\n", __builtin_return_address(0)); return 0; }
Auf diese Weise können Sie die ungültigen Anrufstellen für diese Änderung identifizieren, während das Programm weiterhin "wie beabsichtigt funktioniert" (bei Bedarf können Sie auch den Anrufer des Anrufers abfragen). Beheben Sie jeden schlecht benommenen Ort nacheinander. Das Programm "funktioniert" weiterhin wie gewohnt.
Wenn keine Adressen mehr angezeigt werden, entfernen Sie den Scheck insgesamt. Möglicherweise müssen Sie den einen oder anderen Absturz noch beheben, wenn Sie Pech haben (weil er beim Testen nicht angezeigt wurde), aber das sollte sehr selten vorkommen. In jedem Fall sollte es Ihren Kollegen daran hindern, Sie anzuschreien.
Entfernen Sie ein oder zwei Schecks pro Woche, und schließlich bleiben keine übrig. In der Zwischenzeit geht das Leben weiter und niemand merkt, was Sie überhaupt tun.
TL; DR
Was "aktuelle Versionen von GCC" betrifft, sind Sie für nicht virtuelle Funktionen in Ordnung, aber natürlich kann niemand sagen, was eine zukünftige Version tun könnte. Ich halte es jedoch für sehr unwahrscheinlich, dass eine zukünftige Version dazu führt, dass Ihr Code beschädigt wird. Nicht wenige bestehende Projekte haben diese Art der Überprüfung (ich erinnere mich, dass wir buchstäblich Hunderte davon in Code :: Blocks Code-Vervollständigung hatten, fragen Sie mich nicht warum!). Compilerhersteller möchten wahrscheinlich nicht Dutzende / Hunderte von großen Projektbetreuern absichtlich unglücklich machen, nur um einen Punkt zu beweisen.
Beachten Sie auch den letzten Absatz ("aus logischer Sicht"). Selbst wenn diese Prüfung mit einem zukünftigen Compiler abstürzt und brennt, stürzt sie trotzdem ab und brennt.
Die if(!this) return;
Anweisung ist insofern etwas nutzlos, als sie this
in einem wohlgeformten Programm niemals ein Nullzeiger sein kann (dies bedeutet, dass Sie eine Mitgliedsfunktion für einen Nullzeiger aufgerufen haben). Dies bedeutet natürlich nicht, dass es unmöglich passieren könnte. In diesem Fall sollte das Programm jedoch stark abstürzen oder mit einer Behauptung abbrechen. Ein solches Programm sollte unter keinen Umständen stillschweigend fortgesetzt werden.
Andererseits ist es durchaus möglich, eine Mitgliedsfunktion für ein ungültiges Objekt aufzurufen , das zufällig nicht null ist . Das Überprüfen, ob this
es sich um den Nullzeiger handelt, fängt diesen Fall offensichtlich nicht ab, aber es ist genau das gleiche UB. Abgesehen davon, dass falsches Verhalten verborgen ist, erkennt diese Überprüfung auch nur die Hälfte der problematischen Fälle.
Wenn Sie sich an den Wortlaut der Spezifikation halten, ist die Verwendung von this
(einschließlich der Überprüfung, ob es sich um einen Nullzeiger handelt) ein undefiniertes Verhalten. Insofern handelt es sich streng genommen um eine "Zeitbombe". Ich würde dies jedoch nicht als vernünftig erachten, sowohl aus praktischer als auch aus logischer Sicht.
- Aus praktischer Sicht spielt es keine Rolle, ob Sie einen ungültigen Zeiger lesen , solange Sie ihn nicht dereferenzieren . Ja, streng genommen ist dies nicht erlaubt. Ja, theoretisch könnte jemand eine CPU bauen, die beim Laden ungültige Zeiger und Fehler überprüft . Leider ist dies nicht der Fall, wenn Sie real sind.
- Von einem logischen Standpunkt aus, unter der Annahme , dass der Scheck wird die Luft zu sprengen, wird es immer noch nicht geschehen. Damit diese Anweisung ausgeführt werden kann, muss die Member-Funktion aufgerufen werden und verwendet (virtuell oder nicht, inline oder nicht) einen ungültigen Wert
this
, den sie im Funktionskörper zur Verfügung stellt. Wenn ein illegitimer Gebrauch this
explodiert, wird es auch der andere tun. Daher ist die Prüfung veraltet, da das Programm bereits früher abstürzt.
nb: Diese Prüfung ist der "sicheren Löschsprache" sehr ähnlich, auf die
nullptr
nach dem Löschen ein Zeiger gesetzt wird (mithilfe eines Makros oder einer Vorlagenfunktion
safe_delete
). Vermutlich ist dies "sicher", da es nicht abstürzt, wenn derselbe Zeiger zweimal gelöscht wird. Einige Leute fügen sogar eine überflüssige hinzu
if(!ptr) delete ptr;
.
Wie Sie wissen,
operator delete
ist dies garantiert ein No-Op für einen Nullzeiger. Dies bedeutet nicht mehr und nicht weniger als durch Setzen eines Zeigers auf den Nullzeiger. Sie haben die einzige Möglichkeit, eine doppelte Löschung zu erkennen, erfolgreich ausgeschlossen (dies ist ein Programmfehler, der behoben werden muss!). Es ist nicht "sicherer", sondern verbirgt falsches Programmverhalten. Wenn Sie ein Objekt zweimal löschen, das Programm
sollte hart abstürzen.
Sie sollten entweder einen gelöschten Zeiger in Ruhe lassen oder, wenn Sie auf Manipulationen bestehen, ihn auf einen ungültigen Zeiger ungleich Null setzen (z. B. die Adresse einer speziellen "ungültigen" globalen Variablen oder einen magischen Wert, wie
-1
wenn Sie wollen - Sie sollten jedoch nicht versuchen, den Absturz zu
betrügen und zu verbergen, wenn er auftreten soll.