2016-07-27 13 views
1

So ein bisschen mehr über die Unterschiede zwischen C-style-wirft, ich versuche static_cast, dynamic_cast und ich beschlossen, zu lernen um zu versuchen, dieses Beispiel, das die Unterschiede zwischen C-style-wirft und static_cast ziemlich gut widerspiegelt.Methodenaufruf nach ungültigen C-Casts arbeitet

class B 
{ 
public: 
    void hi() { cout << "hello" << endl; } 
}; 

class D: public B {}; 

class A {}; 

int main() 
{ 
    A* a = new A(); 
    B* b = (B*)a; 
    b->hi(); 
} 

Gut, der Code-Snippet sollte bedenken, daß die C-Stil Guss sehr schief geht und die schlechte Besetzung ist nicht nachgewiesen. Teilweise passiert es so. Die schlechte Besetzung wird nicht erkannt, aber ich war überrascht, als das Programm, statt bei b->hi(); abstürzt, es auf dem Bildschirm das Wort "Hallo" gedruckt.

Nun, warum passiert das? Welches Objekt wurde verwendet, um eine solche Methode aufzurufen, wenn kein B-Objekt instanziiert ist? Ich verwende g ++ zum kompilieren.

+0

Casting zu einem nicht verwandten Typ wie diesem (was in C++ effektiv 'reinterpret_cast' ist) ist undefiniertes Verhalten. Dies bedeutet, dass die Implementierung tun kann, was sie will. – Yuushi

+0

TIPP: selbst eine kaputte Uhr zeigt zweimal am Tag die richtige Zeit. –

+0

Ein C-Style-Cast ist genau so, wie der Programmierer dem Compiler sagt: "Vertrau mir, das ist B-Objekt". Der Compiler hat keine andere Wahl, als sich zu fügen. Was passiert, wenn es läuft, ist nicht definiert. Dies ist genau der Grund, warum C-Style-Casts so gefährlich sind. –

Antwort

3

Wie andere sagten, ist es undefiniertes Verhalten.

Warum funktioniert es aber? Dies liegt wahrscheinlich daran, dass der Funktionsaufruf während der Kompilierungszeit statisch verknüpft ist (es handelt sich nicht um eine virtuelle Funktion). Die Funktion B::hi() existiert so, dass sie aufgerufen wird. Versuchen Sie, die Variable class B hinzuzufügen und verwenden Sie sie in der Funktion hi(). Dann werden Sie Problem (Müll-Wert) auf dem Bildschirm sehen:

class B 
{ 
public: 
    void hi() { cout << "hello, my value is " << x << endl; } 

private: 
    int x = 5; 
}; 

Andernfalls könnten Sie Funktion hi() virtuellen machen.Dann Funktion dynamisch verbunden ist, zur Laufzeit und Programm stürzt sofort:

class B 
{ 
public: 
    virtual void hi() { cout << "hello" << endl; } 
}; 
+1

Beachten Sie, dass der Standard nicht garantiert, dass einer Ihrer Vorschläge zu einem Absturz führen muss. – user2079303

+0

@ user2079303 Das ist wahr. Es kann vorkommen, dass mein Computer nach einem solchen Anruf die Mission zum Mars startet oder mit Terminator: Genisys beginnt. Aber wenn er gcc verwendet, ist es wahrscheinlich, dass die Ergebnisse genau wie meins sein werden: ein seltsamer Wert zuerst und ein Absturz im zweiten Beispiel. – Lehu

1

Nun, warum passiert das?

Weil es passieren kann. Alles kann passieren. Das Verhalten ist undefined.

Die Tatsache, dass etwas Unerwartetes passiert ist, zeigt gut, warum UB so gefährlich ist. Wenn es immer einen Absturz verursachte, wäre es viel einfacher, damit umzugehen.

Welche Objekt verwendet wurde, ein solches Verfahren

Wahrscheinlich zu nennen, der Compiler vertraut Ihnen blind und geht davon aus, dass b tut Punkt auf ein Objekt vom Typ B (was es nicht). Es würde wahrscheinlich das spitze Gedächtnis verwenden, als ob die Annahme wahr wäre. Die Elementfunktion hat nicht auf den Speicher zugegriffen, der zu dem Objekt gehört, und das Verhalten ist so, als ob ein Objekt vom richtigen Typ gewesen wäre.

Als undefiniert, könnte das Verhalten völlig anders sein. Wenn Sie versuchen, das Programm erneut auszuführen, garantiert der Standard nicht, dass Dämonen nicht aus Ihrer Nase fliegen werden.

1

Dies funktioniert nur wegen der Umsetzung der hi() Methode selbst, und der eigentümliche Teil der C++ spec undefiniertes Verhalten genannt.

Casting mit einem C-Style-Cast zu einem inkompatiblen Zeigertyp ist undefiniertes Verhalten - buchstäblich alles kann passieren.

In diesem Fall hat der Compiler offensichtlich beschlossen, nur Sie vertrauen, und hat beschlossen, zu glauben, dass b zu einer Instanz von B tatsächlich ein gültiger Zeiger ist - dies ist in der Tat alle ein gegossenes C-Stil jemals tun wird, da sie kein Laufzeitverhalten beinhalten. Wenn Sie hi() auf es nennen, funktioniert das Verfahren, weil:

  • Es keine Instanzvariablen zu B gehören, Zugriff hat aber nicht A (in der Tat, ist es keine Instanzvariablen überhaupt Zugriff)
  • Es ist nicht virtuell, es muß also nicht vTable in b ‚s nachgeschlagen wird

es funktioniert daher aufgerufen werden, sondern in fast alle nicht-trivialen Fällen, wie eine ungültige von einem Methodenaufruf gefolgt Besetzung ergäbe bei einem Absturz oder einer Speicherbeschädigung. Und Sie können sich nicht auf diese Art von Verhalten verlassen - undefined bedeutet nicht, dass es jedes Mal dasselbe sein muss, wenn Sie es ausführen. Der Compiler ist vollkommen in seinen Rechten mit diesem Code, um einen Zufallszahlengenerator einzufügen und, beim Generieren einer 1, eine vollständige Kopie der ursprünglichen Doom zu starten. Denken Sie immer daran, wenn etwas mit undefiniertem Verhalten zu funktionieren scheint, weil es morgen vielleicht nicht funktioniert und Sie es so behandeln müssen.