Ist dieser Code gültig (und definiertes Verhalten)?
int &nullReference = *(int*)0;
Sowohl g ++ und Klirren ++ kompilieren , ohne jede Vorwarnung, auch bei der Verwendung von -Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
...
Natürlich ist die Referenz nicht wirklich null, da nicht auf sie zugegriffen werden kann (dies würde bedeuten, dass ein Nullzeiger dereferenziert wird), aber wir könnten überprüfen, ob sie null ist oder nicht, indem wir ihre Adresse überprüfen:
if( & nullReference == 0 ) // null reference
c++
reference
null
language-lawyer
Peoro
quelle
quelle
if (false)
und die Prüfung eliminieren, gerade weil Referenzen sowieso nicht null sein können. Eine besser dokumentierte Version existierte im Linux-Kernel, wo eine sehr ähnliche NULL-Prüfung optimiert wurde: isc.sans.edu/diary.html?storyid=6820Antworten:
Referenzen sind keine Zeiger.
8.3.2 / 1:
1,9 / 4:
Wie Johannes in einer gelöschten Antwort sagt, gibt es Zweifel, ob "Dereferenzieren eines Nullzeigers" kategorisch als undefiniertes Verhalten bezeichnet werden sollte. Dies ist jedoch nicht einer der Fälle, die Zweifel aufkommen lassen, da ein Nullzeiger sicherlich nicht auf ein "gültiges Objekt oder eine gültige Funktion" verweist und innerhalb des Normungsausschusses kein Wunsch besteht, Nullreferenzen einzuführen.
quelle
&*p
alsp
universell zu gelten, dies schließt jedoch undefiniertes Verhalten nicht aus (das seiner Natur nach "zu funktionieren scheint"). und ich bin nicht einverstanden, dass eintypeid
Ausdruck, der versucht, den Typ eines "dereferenzierten Nullzeigers" zu bestimmen, den Nullzeiger tatsächlich dereferenziert. Ich habe Leute gesehen, die ernsthaft darüber gestritten haben, dass&a[size_of_array]
man sich nicht darauf verlassen kann und sollte, und trotzdem ist es einfacher und sicherer, einfach zu schreibena + size_of_array
.*p
bezieht, wannp
ein Nullzeiger ist. C ++ hat derzeit nicht die Vorstellung eines leeren Wertes, den das Problem 232 einführen wollte.typeid
Werken, die auf der Syntax anstatt auf der Semantik basieren. Das heißt, wenn Sie das tuntypeid(0, *(ostream*)0)
Sie tun haben undefinierte Verhalten - nichtbad_typeid
garantiert geworfen werden, auch wenn Sie einen L - Wert übergeben semantisch von einem Null - Pointer - Dereference führt. Aber syntaktisch gesehen ist es auf oberster Ebene keine Dereferenzierung, sondern ein Kommaoperatorausdruck.Die Antwort hängt von Ihrem Standpunkt ab:
Wenn Sie nach dem C ++ - Standard urteilen, können Sie keine Nullreferenz erhalten, da Sie zuerst ein undefiniertes Verhalten erhalten. Nach diesem ersten Auftreten von undefiniertem Verhalten lässt der Standard zu, dass alles passiert. Wenn Sie also schreiben
*(int*)0
, haben Sie bereits ein undefiniertes Verhalten, da Sie aus Sicht des Sprachstandards einen Nullzeiger dereferenzieren. Der Rest des Programms ist irrelevant. Sobald dieser Ausdruck ausgeführt wird, sind Sie aus dem Spiel.In der Praxis können Nullreferenzen jedoch leicht aus Nullzeigern erstellt werden, und Sie werden es erst bemerken, wenn Sie tatsächlich versuchen, auf den Wert hinter der Nullreferenz zuzugreifen. Ihr Beispiel ist möglicherweise etwas zu einfach, da jeder gute Optimierungs-Compiler das undefinierte Verhalten erkennt und einfach alles wegoptimiert, was davon abhängt (die Nullreferenz wird nicht einmal erstellt, sondern wegoptimiert).
Diese Optimierung hängt jedoch vom Compiler ab, um das undefinierte Verhalten zu beweisen, was möglicherweise nicht möglich ist. Betrachten Sie diese einfache Funktion in einer Datei
converter.cpp
:Wenn der Compiler diese Funktion sieht, weiß er nicht, ob der Zeiger ein Nullzeiger ist oder nicht. Es wird also nur Code generiert, der jeden Zeiger in die entsprechende Referenz verwandelt. (Übrigens: Dies ist ein Noop, da Zeiger und Referenzen im Assembler genau dasselbe Tier sind.) Nun, wenn Sie eine andere Datei
user.cpp
mit dem Code habenDer Compiler weiß nicht, dass
toReference()
der übergebene Zeiger dereferenziert wird, und geht davon aus, dass er eine gültige Referenz zurückgibt, die in der Praxis zufällig eine Nullreferenz ist. Der Aufruf ist erfolgreich, aber wenn Sie versuchen, die Referenz zu verwenden, stürzt das Programm ab. Hoffnungsvoll. Der Standard lässt zu, dass alles passiert, einschließlich des Aussehens von rosa Elefanten.Sie fragen sich vielleicht, warum dies relevant ist, schließlich wurde das undefinierte Verhalten bereits im Inneren ausgelöst
toReference()
. Die Antwort lautet Debugging: Nullreferenzen können sich wie Nullzeiger verbreiten und vermehren. Wenn Sie nicht wissen, dass Nullreferenzen vorhanden sein können, und lernen, diese nicht zu erstellen, müssen Sie möglicherweise einige Zeit damit verbringen, herauszufinden, warum Ihre Mitgliedsfunktion abstürzt, wenn nur versucht wird, ein einfaches altesint
Mitglied zu lesen (Antwort: die Instanz) Im Aufruf des Mitglieds befand sich eine Nullreferenz, ebensothis
wie ein Nullzeiger, und Ihr Mitglied wird so berechnet, dass es sich als Adresse befindet. 8).Wie wäre es also mit der Suche nach Nullreferenzen? Du hast die Zeile gegeben
in deiner Frage. Nun, das wird nicht funktionieren: Gemäß dem Standard haben Sie undefiniertes Verhalten, wenn Sie einen Nullzeiger dereferenzieren, und Sie können keine Nullreferenz erstellen, ohne einen Nullzeiger zu dereferenzieren, sodass Nullreferenzen nur im Bereich des undefinierten Verhaltens existieren. Da Ihr Compiler möglicherweise davon ausgeht, dass Sie kein undefiniertes Verhalten auslösen, kann er davon ausgehen, dass es keine Nullreferenz gibt (obwohl er leicht Code ausgibt, der Nullreferenzen generiert!). Als solches sieht es die
if()
Bedingung, kommt zu dem Schluss, dass es nicht wahr sein kann, und wirft einfach die gesamteif()
Aussage weg . Mit der Einführung von Verbindungszeitoptimierungen ist es offensichtlich unmöglich geworden, auf robuste Weise nach Nullreferenzen zu suchen.TL; DR:
Nullreferenzen sind eine schreckliche Existenz:
Ihre Existenz scheint unmöglich (= nach Standard),
aber sie existieren (= nach dem generierten Maschinencode),
aber Sie können sie nicht sehen, wenn sie existieren (= Ihre Versuche werden optimiert),
aber sie können Sie trotzdem töten, ohne es zu wissen (= Ihr Programm stürzt an seltsamen Stellen ab oder schlimmer.
Ihre einzige Hoffnung ist, dass sie nicht existieren (= schreiben Sie Ihr Programm, um sie nicht zu erstellen).
Ich hoffe, das wird dich nicht verfolgen!
quelle
Wenn Sie eine Möglichkeit finden möchten, null in einer Aufzählung von Singleton-Objekten darzustellen, ist es eine schlechte Idee, null (de) zu referenzieren (it C ++ 11, nullptr).
Warum nicht das statische Singleton-Objekt, das NULL innerhalb der Klasse darstellt, wie folgt deklarieren und einen Operator für die Umwandlung in einen Zeiger hinzufügen, der nullptr zurückgibt?
Bearbeiten: Mehrere Fehlertypen wurden korrigiert und eine if-Anweisung in main () hinzugefügt, um zu testen, ob der Operator für die Umwandlung in einen Zeiger tatsächlich funktioniert (was ich vergessen habe .. mein schlechtes) - 10. März 2015 -
quelle
clang ++ 3.5 warnt sogar davor:
quelle