Wann führt das Aufrufen einer Memberfunktion für eine Nullinstanz zu undefiniertem Verhalten?

Lesezeit: 8 Minuten

Wann fuhrt das Aufrufen einer Memberfunktion fur eine Nullinstanz zu
GManNickG

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) zum Absturz bringen, weil es kein entsprechendes Mitglied gibt x für den Nullzeiger. In der Praxis, (a) stürzt nicht ab, weil die this Zeiger wird nie verwendet.

Weil (b) dereferenziert die this Zeiger ((*this).x = 5;), und this null ist, tritt das Programm in undefiniertes Verhalten ein, da die Dereferenzierung von null immer als undefiniertes Verhalten bezeichnet wird.

Tut (a) zu undefiniertem Verhalten führen? Was ist, wenn beide Funktionen (und x) sind statisch?

  • Wenn beide Funktionen sind statischwie könnte x nach innen verwiesen werden baz? (x ist eine nicht statische Elementvariable)

    – legends2k

    29. September 2010 um 16:09 Uhr

  • @legends2k: Vorgeben x wurde auch statisch gemacht. 🙂

    – GManNickG

    29. September 2010 um 21:25 Uhr

  • Sicherlich, aber für den Fall (a) funktioniert es in allen Fällen gleich, dh die Funktion wird aufgerufen. Wenn Sie jedoch den Wert des Zeigers von 0 auf 1 ersetzen (z. B. durch reinterpret_cast), stürzt es fast immer ab. Stellt die Wertzuweisung von 0 und damit NULL, wie im Fall a, etwas Besonderes für den Compiler dar? Warum stürzt es immer ab, wenn ihm ein anderer Wert zugewiesen wird?

    – Siddharth Shankaran

    28. Dezember 2010 um 12:18 Uhr

  • Interessant: Mit der nächsten Überarbeitung von C++ soll es überhaupt keine Dereferenzierung von Zeigern mehr geben. Wir werden jetzt Umleitung durchführen durch Zeiger. Um mehr zu erfahren, führen Sie bitte eine Umleitung über diesen Link durch: N3362

    – James McNellis

    5. Juni 2012 um 21:08 Uhr

  • Das Aufrufen einer Member-Funktion für einen Nullzeiger ist immer undefiniertes Verhalten. Wenn ich mir nur Ihren Code ansehe, kann ich bereits das undefinierte Verhalten spüren, das mir langsam den Hals hinaufkriecht!

    – fredoverflow

    27. Juli 2012 um 9:02 Uhr


Wann fuhrt das Aufrufen einer Memberfunktion fur eine Nullinstanz zu
GManNickG

Beide (a) und (b) zu undefiniertem Verhalten führen. Es ist immer ein undefiniertes Verhalten, eine Memberfunktion über einen Nullzeiger aufzurufen. Wenn die Funktion statisch ist, ist sie technisch ebenfalls undefiniert, aber es gibt einige Streitigkeiten.


Als Erstes müssen Sie verstehen, warum es ein undefiniertes Verhalten ist, einen Nullzeiger zu dereferenzieren. In C++03 gibt es hier tatsächlich ein wenig Mehrdeutigkeit.

Obwohl “Dereferenzieren eines Nullzeigers führt zu undefiniertem Verhalten” sowohl in §1.9/4 als auch in §8.3.2/4 in den Anmerkungen erwähnt wird, wird es nie ausdrücklich erwähnt. (Hinweise sind nicht normativ.)

Man kann jedoch versuchen, es aus §3.10/2 abzuleiten:

Ein lvalue bezieht sich auf ein Objekt oder eine Funktion.

Beim Dereferenzieren ist das Ergebnis ein Lvalue. Ein Nullzeiger nicht beziehen sich auf ein Objekt, daher haben wir ein undefiniertes Verhalten, wenn wir den lvalue verwenden. Das Problem ist, dass der vorherige Satz nie erwähnt wird. Was bedeutet es also, den lvalue zu “verwenden”? Generieren Sie es überhaupt oder verwenden Sie es im formelleren Sinne der Durchführung einer lvalue-to-rvalue-Konvertierung?

Unabhängig davon kann es definitiv nicht in einen rvalue konvertiert werden (§4.1/1):

Wenn das Objekt, auf das sich der lvalue bezieht, kein Objekt des Typs T und kein Objekt eines von T abgeleiteten Typs ist, oder wenn das Objekt nicht initialisiert ist, hat ein Programm, das diese Konvertierung erfordert, ein undefiniertes Verhalten.

Hier ist es definitiv undefiniertes Verhalten.

Die Mehrdeutigkeit ergibt sich daraus, ob es sich um ein undefiniertes Verhalten gegenüber Ehrerbietung handelt oder nicht aber nicht verwenden den Wert von einem ungültigen Zeiger (d. h. einen lvalue abrufen, aber nicht in einen rvalue konvertieren). Wenn nicht, dann int *i = 0; *i; &(*i); ist wohldefiniert. Das ist ein aktives Thema.

Wir haben also eine strenge Ansicht “Nullzeiger dereferenzieren, undefiniertes Verhalten erhalten” und eine schwache Ansicht “Nullzeiger dereferenzieren, undefiniertes Verhalten erhalten”.

Jetzt betrachten wir die Frage.


Jawohl, (a) führt zu undefiniertem Verhalten. In der Tat, wenn this ist dann null unabhängig vom Inhalt der Funktion das Ergebnis ist undefiniert.

Daraus folgt aus §5.2.5/3:

Wenn E1 vom Typ „Zeiger auf Klasse X“ hat, dann ist der Ausdruck E1->E2 wird in die entsprechende Form umgewandelt (*(E1)).E2;

*(E1) führt bei strenger Auslegung zu undefiniertem Verhalten, und .E2 wandelt es in einen rvalue um, wodurch es für die schwache Interpretation zu einem undefinierten Verhalten wird.

Es folgt auch, dass es sich um ein undefiniertes Verhalten direkt aus (§9.3.1/1) handelt:

Wenn eine nichtstatische Elementfunktion einer Klasse X für ein Objekt aufgerufen wird, das nicht vom Typ X ist oder einen von X abgeleiteten Typ hat, ist das Verhalten undefiniert.


Bei statischen Funktionen macht die strikte versus schwache Interpretation den Unterschied. Genau genommen ist es undefiniert:

Auf ein statisches Mitglied kann unter Verwendung der Zugriffssyntax für Klassenmitglieder verwiesen werden, wobei in diesem Fall der Objektausdruck ausgewertet wird.

Das heißt, es wird so ausgewertet, als wäre es nicht statisch, und wir dereferenzieren erneut einen Nullzeiger mit (*(E1)).E2.

Allerdings, weil E1 wird nicht in einem statischen Elementfunktionsaufruf verwendet, wenn wir die schwache Interpretation verwenden, ist der Aufruf wohldefiniert. *(E1) ergibt einen lvalue, die statische Funktion wird aufgelöst, *(E1) verworfen und die Funktion aufgerufen. Es gibt keine lvalue-zu-rvalue-Konvertierung, also gibt es kein undefiniertes Verhalten.

In C++0x bleibt die Mehrdeutigkeit ab n3126 bestehen. Seien Sie vorerst sicher: Verwenden Sie die strenge Interpretation.

  • +1. Um die Pedanterie fortzusetzen, wurde unter der “schwachen Definition” die nichtstatische Elementfunktion nicht “für ein Objekt aufgerufen, das nicht vom Typ X ist”. Es wurde für einen lvalue aufgerufen, der überhaupt kein Objekt ist. Die vorgeschlagene Lösung fügt also der von Ihnen zitierten Klausel den Text “oder wenn der lvalue ein leerer lvalue ist” hinzu.

    – Steve Jessop

    18. März 2010 um 23:28 Uhr


  • Könnten Sie ein wenig klarstellen? Wie lauten insbesondere die Nummern der Ausgaben bei Ihren Links „Geschlossene Ausgabe“ und „Aktive Ausgabe“? Wenn dies ein geschlossenes Problem ist, was genau ist die Ja/Nein-Antwort für statische Funktionen? Ich habe das Gefühl, dass ich den letzten Schritt verpasse, um Ihre Antwort zu verstehen.

    – Brooks Moses

    18. März 2010 um 23:30 Uhr

  • Ich glaube nicht, dass der CWG-Defekt 315 so „geschlossen“ ist, wie seine Anwesenheit auf der Seite „Geschlossene Probleme“ vermuten lässt. Die Begründung besagt, dass es erlaubt sein sollte, weil “*p ist kein Fehler, wenn p ist null, es sei denn, der lvalue wird in einen rvalue konvertiert.” Dies beruht jedoch auf dem Konzept eines “leeren lvalue”, das Teil der vorgeschlagenen Resolution zu ist CWG-Defekt 232, die aber nicht angenommen wurde. Bei der Sprache in C++03 und C++0x ist die Dereferenzierung des Nullzeigers also immer noch undefiniert, selbst wenn keine lvalue-zu-rvalue-Konvertierung erfolgt.

    – James McNellis

    8. Juni 2010 um 3:42 Uhr

  • @JamesMcNellis: Nach meinem Verständnis, wenn p waren eine Hardwareadresse, die beim Lesen eine Aktion auslösen würde, aber nicht deklariert wurden volatiledie Aussage *p; wäre nicht erforderlich, wäre aber erlaubt, um diese Adresse tatsächlich zu lesen; die Aussage &(*p);, wäre dies jedoch untersagt. Wenn *p wurden volatile, wäre das Lesen erforderlich. In beiden Fällen, wenn der Zeiger ungültig ist, kann ich nicht sehen, warum die erste Anweisung nicht Undefined Behavior wäre, aber ich kann auch nicht sehen, warum die zweite Anweisung so wäre.

    – Superkatze

    22. Januar 2014 um 0:20 Uhr

  • “.E2 wandelt es in einen rvalue um, ” – Äh, nein, tut es nicht

    – MM

    3. Januar 2018 um 2:44 Uhr

1647256210 223 Wann fuhrt das Aufrufen einer Memberfunktion fur eine Nullinstanz zu
Mark Lösegeld

Offensichtlich undefiniert bedeutet, dass es ist nicht definiert, aber manchmal kann es vorhersehbar sein. Die Informationen, die ich gleich bereitstellen werde, sollten sich niemals auf funktionierenden Code verlassen, da dies sicherlich nicht garantiert ist, aber beim Debuggen nützlich sein kann.

Sie könnten denken, dass das Aufrufen einer Funktion für einen Objektzeiger den Zeiger dereferenziert und UB verursacht. Wenn die Funktion nicht virtuell ist, hat der Compiler sie in der Praxis in einen einfachen Funktionsaufruf umgewandelt, der den Zeiger als ersten Parameter übergibt Das, Umgehen der Dereferenzierung und Erstellen einer Zeitbombe für die aufgerufene Member-Funktion. Wenn die Member-Funktion auf keine Member-Variablen oder virtuellen Funktionen verweist, kann sie tatsächlich ohne Fehler erfolgreich ausgeführt werden. Denken Sie daran, dass Erfolg in das Universum des „Undefinierten“ fällt!

MFC-Funktion von Microsoft GetSafeHwnd tatsächlich auf dieses Verhalten angewiesen. 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).

  • GetSafeHwnd führt zuerst eine !this-Prüfung durch und gibt NULL zurück, wenn es wahr ist. Dann beginnt es einen SEH-Rahmen und dereferenziert den Zeiger. Wenn es eine Speicherzugriffsverletzung (0xc0000005) gibt, wird dies abgefangen und NULL wird an den Anrufer zurückgegeben 🙂 Andernfalls wird das HWND zurückgegeben.

    – Петър Петров

    8. Oktober 2014 um 2:18 Uhr


  • @ПетърПетров Es ist schon ein paar Jahre her, dass ich mir den Code für angeschaut habe GetSafeHwnd, es ist möglich, dass sie es seitdem verbessert haben. Und vergessen Sie nicht, dass sie Insiderwissen über die Funktionsweise des Compilers haben!

    – Markieren Sie Lösegeld

    8. Oktober 2014 um 2:44 Uhr

  • Ich nenne ein Beispiel für eine mögliche Implementierung, die den gleichen Effekt hat. Was es wirklich tut, ist, mit einem Debugger rückentwickelt zu werden 🙂

    – Петър Петров

    8. Oktober 2014 um 16:40 Uhr

  • “Sie haben Insiderwissen über die Arbeitsweise des Compilers!” – die Ursache ewiger Probleme für Projekte wie MinGW, die versuchen, g++ zu erlauben, Code zu kompilieren, der die Windows-API aufruft

    – MM

    10. Dezember 2015 um 11:15 Uhr

  • @AnOccasionalCashew ja, das ist mir bewusst, und es war unverantwortlich von meiner Seite, es nicht anzusprechen. Mein Lieblingsbeitrag zu diesem Thema ist Undefiniertes Verhalten kann zu Zeitreisen führen (unter anderem, aber Zeitreisen sind am verrücktesten).

    – Markieren Sie Lösegeld

    10. Dezember 2020 um 4:22 Uhr

1001240cookie-checkWann führt das Aufrufen einer Memberfunktion für eine Nullinstanz zu undefiniertem Verhalten?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy