Betrachten Sie den folgenden Code:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
Wir erwarten (b)
einen Absturz, da es kein entsprechendes Mitglied x
für den Nullzeiger gibt. In der Praxis (a)
stürzt nicht ab, da der this
Zeiger nie verwendet wird.
Da (b)
der this
Zeiger ( (*this).x = 5;
) dereferenziert und this
null ist, gibt das Programm ein undefiniertes Verhalten ein, da das Dereferenzieren von null immer als undefiniertes Verhalten bezeichnet wird.
Führt (a)
zu undefiniertem Verhalten? Was ist, wenn beide Funktionen (und x
) statisch sind?
x
wurde auch statisch gemacht. :)Antworten:
Beides
(a)
und(b)
führen zu undefiniertem Verhalten. Es ist immer undefiniertes Verhalten, eine Mitgliedsfunktion über einen Nullzeiger aufzurufen. Wenn die Funktion statisch ist, ist sie auch technisch undefiniert, aber es gibt einige Streitigkeiten.Das erste, was zu verstehen ist, ist, warum es undefiniertes Verhalten ist, einen Nullzeiger zu dereferenzieren. In C ++ 03 gibt es hier tatsächlich ein bisschen Mehrdeutigkeit.
Obwohl "das Dereferenzieren eines Nullzeigers zu undefiniertem Verhalten führt" in den Anmerkungen sowohl in §1.9 / 4 als auch in §8.3.2 / 4 erwähnt wird, wird dies niemals explizit angegeben. (Notizen sind nicht normativ.)
Man kann jedoch versuchen, es aus §3.10 / 2 abzuleiten:
Bei der Dereferenzierung ist das Ergebnis ein Wert. Ein Nullzeiger nicht auf ein Objekt. Wenn wir also den Wert l verwenden, haben wir ein undefiniertes Verhalten. Das Problem ist, dass der vorherige Satz nie angegeben wird. Was bedeutet es also, den Wert zu "verwenden"? Generieren Sie es einfach überhaupt oder verwenden Sie es im formaleren Sinne, um eine Wert-zu-Wert-Konvertierung durchzuführen?
Unabhängig davon kann es definitiv nicht in einen Wert umgewandelt werden (§4.1 / 1):
Hier ist es definitiv undefiniertes Verhalten.
Die Mehrdeutigkeit ergibt sich daraus, ob es sich um ein undefiniertes Verhalten zur Zurückhaltung handelt oder nicht, aber den Wert eines ungültigen Zeigers nicht verwendet (dh einen l-Wert erhalten, ihn aber nicht in einen r-Wert konvertieren). Wenn nicht, dann
int *i = 0; *i; &(*i);
ist genau definiert. Dies ist ein aktives Problem .Wir haben also eine strikte Ansicht "Dereferenzieren eines Nullzeigers, Erhalten eines undefinierten Verhaltens" und eine schwache Ansicht "Verwenden eines dereferenzierten Nullzeigers, Erhalten eines undefinierten Verhaltens".
Nun betrachten wir die Frage.
Ja,
(a)
führt zu undefiniertem Verhalten. In der Tat, wennthis
dann null ist das Ergebnis unabhängig vom Inhalt der Funktion undefiniert.Dies folgt aus §5.2.5 / 3:
*(E1)
wird zu undefiniertem Verhalten mit einer strengen Interpretation führen, und.E2
konvertiert es in einen r-Wert, wodurch es für die schwache Interpretation zu undefiniertem Verhalten wird.Daraus folgt auch, dass es sich um ein undefiniertes Verhalten direkt aus (§9.3.1 / 1) handelt:
Bei statischen Funktionen macht die strikte gegenüber der schwachen Interpretation den Unterschied. Genau genommen ist es undefiniert:
Das heißt, es wird so ausgewertet, als wäre es nicht statisch, und wir dereferenzieren erneut einen Nullzeiger mit
(*(E1)).E2
.Da dies
E1
jedoch nicht in einem statischen Elementfunktionsaufruf verwendet wird, ist der Aufruf gut definiert, wenn wir die schwache Interpretation verwenden.*(E1)
ergibt sich ein lWert, die statische Funktion wird aufgelöst,*(E1)
verworfen und die Funktion aufgerufen. Es gibt keine Konvertierung von lWert in rWert, daher gibt es kein undefiniertes Verhalten.In C ++ 0x bleibt ab n3126 die Mehrdeutigkeit bestehen. Seien Sie vorerst sicher: Verwenden Sie die strenge Interpretation.
quelle
*p
es kein Fehler ist, wennp
es null ist, es sei denn, der l-Wert wird in einen r-Wert konvertiert." Dies beruht jedoch auf dem Konzept eines "leeren Werts", das Teil der vorgeschlagenen Lösung für den CWG-Defekt 232 ist , aber nicht angenommen wurde. Mit der Sprache in C ++ 03 und C ++ 0x ist die Dereferenzierung des Nullzeigers immer noch undefiniert, selbst wenn keine Konvertierung von Wert zu Wert erfolgt.p
eine Hardwareadresse beim Lesen eine Aktion auslösen würde, aber nicht deklariert würdevolatile
, wäre die Anweisung*p;
nicht erforderlich, aber erlaubt , diese Adresse tatsächlich zu lesen. Die Aussage&(*p);
wäre jedoch verboten. Wenn*p
warenvolatile
, würde die Lese erforderlich. In beiden Fällen, wenn der Zeiger ungültig ist, kann ich nicht sehen, wie die erste Anweisung nicht undefiniertes Verhalten wäre, aber ich kann auch nicht sehen, warum die zweite Anweisung sein würde.Offensichtlich bedeutet undefiniert, dass es nicht definiert ist , aber manchmal kann es vorhersehbar sein. Die Informationen, die ich bereitstellen werde, sollten für den Arbeitscode niemals verwendet werden, da sie sicherlich nicht garantiert sind, aber beim Debuggen nützlich sein können.
Sie könnten denken, dass das Aufrufen einer Funktion für einen Objektzeiger den Zeiger dereferenziert und UB verursacht. In der Praxis , wenn die Funktion nicht virtuell ist, hat sich der Compiler es zu einem einfachen Funktionsaufruf übergeben den Zeiger als ersten Parameter konvertiert diese , die dereferenzieren Umgehung und eine Zeitbombe für die gerufene Memberfunktion zu schaffen. Wenn die Member-Funktion keine Member-Variablen oder virtuellen Funktionen referenziert, ist sie möglicherweise tatsächlich fehlerfrei erfolgreich. Denken Sie daran, dass Erfolg in das Universum von "undefiniert" fällt!
Die MFC-Funktion GetSafeHwnd von Microsoft basiert tatsächlich auf diesem Verhalten. Ich weiß nicht, was sie geraucht haben.
Wenn Sie eine virtuelle Funktion aufrufen, muss der Zeiger dereferenziert werden, um zur vtable zu gelangen, und Sie werden mit Sicherheit UB erhalten (wahrscheinlich ein Absturz, aber denken Sie daran, dass es keine Garantien gibt).
quelle
GetSafeHwnd
. Es ist möglich, dass sie ihn seitdem verbessert haben. Und vergessen Sie nicht, dass sie Insiderwissen über die Funktionsweise des Compilers haben!