3158 Stimmen

Wann sollten static_cast, dynamic_cast, const_cast und reinterpret_cast verwendet werden?

Was sind die richtigen Verwendungszwecke von:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C-Stil Guss (type)value
  • Guss im Funktionsstil type(value)

Wie kann man entscheiden, welche in welchen Fällen verwendet werden sollen?

4 Stimmen

Einige nützliche konkrete Beispiele für die Verwendung verschiedener Arten von Gussformen finden Sie in der ersten Antwort auf eine ähnliche Frage in dieses andere Thema .

5 Stimmen

Sie können oben wirklich gute Antworten auf Ihre Frage finden. Aber ich möchte hier noch einen weiteren Punkt anbringen, @e.James "Es gibt nichts, was diese neuen C++ Cast-Operatoren tun können und C Style Cast nicht. Sie sind mehr oder weniger für die bessere Lesbarkeit des Codes hinzugefügt worden."

3098voto

coppro Punkte 14158

static_cast ist der erste Wurf, den Sie versuchen sollten. Es macht Dinge wie implizite Konvertierungen zwischen Typen (wie int à float oder Zeiger auf void* ), und sie kann auch explizite (oder implizite) Konvertierungsfunktionen aufrufen. In vielen Fällen ist die explizite Angabe von static_cast ist nicht notwendig, aber es ist wichtig zu wissen, dass die T(something) Syntax ist äquivalent zu (T)something und sollte vermieden werden (mehr dazu später). A T(something, something_else) ist jedoch sicher und wird garantiert den Konstruktor aufrufen.

static_cast kann auch durch Vererbungshierarchien wirken. Beim Casting nach oben (in Richtung einer Basisklasse) ist es nicht notwendig, aber beim Casting nach unten kann es verwendet werden, solange es nicht durch virtual Vererbung. Es findet jedoch keine Überprüfung statt, und es ist ein undefiniertes Verhalten, wenn static_cast in einer Hierarchie zu einem Typ, der eigentlich nicht der Typ des Objekts ist.


const_cast kann verwendet werden, um zu entfernen oder hinzuzufügen const zu einer Variablen; kein anderer C++-Cast ist in der Lage, ihn zu entfernen (nicht einmal reinterpret_cast ). Es ist wichtig zu beachten, dass die Änderung eines früheren const Wert ist nur dann undefiniert, wenn die ursprüngliche Variable const wenn Sie damit die const einen Verweis auf etwas aus, das nicht mit const ist es sicher. Dies kann nützlich sein, wenn man Mitgliedsfunktionen überlädt, die auf const zum Beispiel. Es kann auch verwendet werden, um hinzuzufügen const zu einem Objekt, z. B. um eine Überladung einer Mitgliedsfunktion aufzurufen.

const_cast funktioniert in ähnlicher Weise auch bei volatile aber das ist weniger üblich.


dynamic_cast wird ausschließlich für die Behandlung von Polymorphismus verwendet. Sie können einen Zeiger oder eine Referenz auf einen polymorphen Typ in einen beliebigen anderen Klassentyp umwandeln (ein polymorpher Typ hat mindestens eine virtuelle Funktion, die deklariert oder geerbt wurde). Sie können damit nicht nur nach unten casten, sondern auch seitwärts oder sogar in eine andere Kette. Die dynamic_cast sucht das gewünschte Objekt und gibt es nach Möglichkeit zurück. Wenn das nicht möglich ist, gibt es zurück nullptr im Falle eines Zeigers, oder werfen std::bad_cast im Falle eines Verweises.

dynamic_cast hat allerdings einige Einschränkungen. Es funktioniert nicht, wenn es mehrere Objekte desselben Typs in der Vererbungshierarchie gibt (der so genannte "gefürchtete Diamant") und Sie nicht mit virtual Vererbung. Es kann auch nur durch die öffentliche Vererbung gehen - es wird immer scheitern, durch protected o private Vererbung. Dies ist jedoch selten ein Problem, da solche Formen der Vererbung selten sind.


reinterpret_cast ist der gefährlichste Wurf und sollte nur sehr sparsam eingesetzt werden. Er wandelt einen Typ direkt in einen anderen um - wie z.B. das Casting des Wertes von einem Zeiger in einen anderen oder das Speichern eines Zeigers in einer int oder alle möglichen anderen unangenehmen Dinge. Im Großen und Ganzen ist die einzige Garantie, die Sie mit reinterpret_cast ist, dass Sie normalerweise genau denselben Wert erhalten, wenn Sie das Ergebnis auf den ursprünglichen Typ zurückführen (aber no wenn der Zwischentyp kleiner ist als der ursprüngliche Typ). Es gibt eine Reihe von Konvertierungen, die reinterpret_cast auch nicht tun kann. Es wird hauptsächlich für besonders seltsame Konvertierungen und Bitmanipulationen verwendet, wie das Umwandeln eines Rohdatenstroms in tatsächliche Daten oder das Speichern von Daten in den niedrigen Bits eines Zeigers auf ausgerichtete Daten.


C-Stil Guss y Funktionsguss sind Würfe mit (type)object o type(object) und sind funktional gleichwertig. Sie sind definiert als der erste der folgenden Fälle, der erfolgreich ist:

  • const_cast
  • static_cast (allerdings ohne Berücksichtigung der Zugangsbeschränkungen)
  • static_cast (siehe oben), dann const_cast
  • reinterpret_cast
  • reinterpret_cast entonces const_cast

Sie kann daher in einigen Fällen als Ersatz für andere Würfe verwendet werden, kann aber extrem gefährlich sein, da sie sich in eine reinterpret_cast und letzteres sollte bevorzugt werden, wenn ein explizites Casting erforderlich ist, es sei denn, Sie sind sich sicher static_cast erfolgreich sein wird oder reinterpret_cast wird scheitern. Selbst dann sollten Sie die längere, explizitere Option in Betracht ziehen.

C-artige Casts ignorieren auch die Zugriffskontrolle bei der Durchführung einer static_cast Das bedeutet, dass sie eine Operation durchführen können, die keine andere Besetzung durchführen kann. Dies ist meist ein Trick, obwohl, und meiner Meinung nach ist nur ein weiterer Grund, um C-Stil Casts zu vermeiden.

1 Stimmen

Ich würde als erste Option dynamic_cast<> empfehlen, was natürlich nur wie oben beschrieben funktioniert. Wann immer Sie dynamic_cast<> verwenden können, wird geprüft, ob der Typ wirklich das ist, was Sie glauben.

23 Stimmen

Dynamic_cast ist nur für polymorphe Typen. Sie brauchen es nur zu verwenden, wenn Sie auf eine abgeleitete Klasse casten. static_cast ist sicherlich die erste Option, es sei denn, Sie benötigen speziell die Funktionalität von dynamic_cast. Es ist keine wundersame Wunderwaffe "type-checking cast" im Allgemeinen.

2 Stimmen

Der Abschnitt über reinterpret_cast zeigt eine Aliasing-Verletzung und damit undefiniertes Verhalten. Wenn Sie diese Art von Typkonvertierung wünschen, ist memcpy der bessere Weg.

432voto

Fred Larson Punkte 58721
  • Utilice dynamic_cast zur Umwandlung von Zeigern/Referenzen innerhalb einer Vererbungshierarchie.

  • Utilice static_cast für gewöhnliche Typkonvertierungen.

  • Utilice reinterpret_cast für die Neuinterpretation von Bitmustern auf niedriger Ebene. Mit äußerster Vorsicht zu verwenden.

  • Utilice const_cast zum Wegwerfen const/volatile . Vermeiden Sie dies, es sei denn, Sie sind mit einem const-inkorrekte API stecken.

13 Stimmen

Seien Sie vorsichtig mit dynamic_cast. Es verlässt sich auf RTTI und dies wird nicht wie erwartet über die Grenzen von Shared Libraries hinweg funktionieren. Da Sie die ausführbare Datei und die Shared Library unabhängig voneinander erstellen, gibt es keine standardisierte Möglichkeit, RTTI über verschiedene Builds hinweg zu synchronisieren. Aus diesem Grund gibt es in der Qt-Bibliothek qobject_cast<>, das die QObject-Typinformationen zur Überprüfung der Typen verwendet.

241voto

Sumit Arora Punkte 4609

(Viele theoretische und konzeptionelle Erläuterungen wurden bereits gegeben)

Nachstehend sind einige der praktische Beispiele als ich noch statisch_gecastet , dynamisch_gecastet , const_cast , uminterpretieren_gießen .

(Siehe auch dies, um die Erläuterung zu verstehen: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast :

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast :

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast :

// *Passwd declared as a const

const unsigned char *Passwd

// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast :

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

43 Stimmen

Die Theorie einiger der anderen Antworten ist gut, aber immer noch verwirrend. Wenn man diese Beispiele sieht, nachdem man die anderen Antworten gelesen hat, macht das alles wirklich Sinn. Das heißt, ohne die Beispiele war ich immer noch unsicher, aber mit ihnen bin ich mir jetzt sicher, was die anderen Antworten bedeuten.

1 Stimmen

Zur letzten Verwendung von reinterpret_cast: Ist dies nicht dasselbe wie die Verwendung von static_cast<char*>(&val) ?

6 Stimmen

@LorenzoBelli Natürlich nicht. Haben Sie es versucht? Letzteres ist kein gültiges C++ und blockiert die Kompilierung. static_cast funktioniert nur zwischen Typen mit definierten Konvertierungen, sichtbarer Beziehung durch Vererbung oder nach/von void * . Für alles andere gibt es andere Gussformen. reinterpret cast zu jeder char * ist erlaubt, um das Lesen der Darstellung eines beliebigen Objekts zu ermöglichen - und einer der einzigen Fälle, in denen dieses Schlüsselwort nützlich ist und nicht ein zügelloser Generator von Implementierungs-/undefiniertem Verhalten. Dies wird jedoch nicht als "normale" Konvertierung betrachtet und ist daher nicht durch die (normalerweise) sehr konservativen static_cast .

149voto

Shital Shah Punkte 54846

Es könnte helfen, wenn Sie ein wenig über Interna wissen...

statisch_gecastet

  • Der C++-Compiler weiß bereits, wie man zwischen Skalertypen wie float à int . Verwenden Sie static_cast für sie.
  • Wenn Sie den Compiler auffordern, vom Typ A à B , static_cast ruft auf. B Der Konstruktor von A als Param. Alternativ dazu, A könnte einen Konvertierungsoperator haben (d.h. A::operator B() ). Wenn B keinen solchen Konstruktor hat, oder A keinen Konvertierungsoperator hat, erhalten Sie einen Kompilierzeitfehler.
  • Besetzung von A* à B* ist immer erfolgreich, wenn sich A und B in der Vererbungshierarchie befinden (oder ungültig sind), andernfalls erhalten Sie einen Kompilierfehler.
  • Habe dich : Wenn Sie einen Basiszeiger in einen abgeleiteten Zeiger umwandeln, aber das tatsächliche Objekt nicht wirklich vom abgeleiteten Typ ist, dann nicht Fehler erhalten. Sie erhalten einen fehlerhaften Zeiger und sehr wahrscheinlich einen Segfault zur Laufzeit. Dasselbe gilt für A& à B& .
  • Habe dich : Cast von Derived nach Base oder umgekehrt erzeugt neu kopieren! Für Leute, die aus C#/Java kommen, kann dies eine große Überraschung sein, denn das Ergebnis ist im Grunde ein abgehacktes Objekt, das aus Derived erstellt wurde.

dynamisch_gecastet

  • dynamic_cast verwendet Laufzeit-Typinformationen, um herauszufinden, ob cast gültig ist. Zum Beispiel, (Base*) à (Derived*) kann fehlschlagen, wenn der Zeiger nicht tatsächlich von einem abgeleiteten Typ ist.
  • Das bedeutet, dass dynamic_cast im Vergleich zu static_cast sehr teuer ist!
  • Pour A* à B* Wenn cast ungültig ist, wird dynamic_cast nullptr zurückgeben.
  • Pour A& à B& wenn cast ungültig ist, löst dynamic_cast die bad_cast-Ausnahme aus.
  • Im Gegensatz zu anderen Casts gibt es einen Laufzeit-Overhead.

const_cast

  • Während static_cast non-const zu const machen kann, kann es nicht andersherum gehen. Der const_cast kann beide Wege gehen.
  • Ein Beispiel, bei dem dies sehr nützlich ist, ist die Iteration durch einen Container wie set<T> die nur ihre Elemente als const zurückgibt, um sicherzustellen, dass Sie den Schlüssel nicht ändern. Wenn Sie jedoch die Absicht haben, die Nicht-Schlüssel-Mitglieder des Objekts zu ändern, sollte dies in Ordnung sein. Sie können const_cast verwenden, um constness zu entfernen.
  • Ein weiteres Beispiel ist, wenn Sie Folgendes implementieren möchten T& SomeClass::foo() wie auch const T& SomeClass::foo() const . Um doppelten Code zu vermeiden, können Sie const_cast anwenden, um den Wert einer Funktion aus einer anderen zurückzugeben.

uminterpretieren_gießen

  • Dies besagt im Grunde, dass man diese Bytes an dieser Speicherstelle als gegebenes Objekt betrachtet.
  • Sie können zum Beispiel 4 Bytes von float auf 4 Bytes von int um zu sehen, wie Bits in float sieht so aus.
  • Wenn die Daten nicht dem Typ entsprechen, kann es natürlich zu einem Segfault kommen.
  • Für diese Besetzung gibt es keinen Laufzeit-Overhead.

0 Stimmen

Ich habe die Informationen zu den Konvertierungsoperatoren hinzugefügt, aber es gibt noch ein paar andere Dinge, die ebenfalls behoben werden sollten, und ich fühle mich nicht wohl dabei, dies zu sehr zu aktualisieren. Die Punkte sind: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime. Sie erhalten UB, was zu einem Segfault zur Laufzeit führen kann, wenn Sie Glück haben. 2. Dynamische Würfe können auch in Cross-Casting verwendet werden. 3. Const Casts können in einigen Fällen zu UB führen. Verwendung von mutable kann eine bessere Wahl sein, um logische Konstanten zu implementieren.

1 Stimmen

@Adrian Sie haben in jeder Hinsicht Recht. Die Antwort ist für Leute auf mehr oder weniger Anfängerniveau geschrieben und ich wollte sie nicht mit allen anderen Komplikationen überwältigen, die damit einhergehen mutable , Kreuzguß usw.

0 Stimmen

@Shital Shah "Cast von Derived zu Base oder umgekehrt erzeugt eine neue Kopie! Für Leute, die aus C#/Java kommen, kann dies eine große Überraschung sein, weil das Ergebnis im Grunde ein abgehacktes Objekt ist, das aus Derived erstellt wurde." Könnten Sie bitte einen einfachen Beispielcode zeigen, um es leichter zu verstehen? Vielen Dank!

25voto

static_cast gegen dynamic_cast gegen reinterpret_cast Interna zu einem Downcast/Upcast

In dieser Antwort möchte ich diese drei Mechanismen anhand eines konkreten Upcast/Downcast-Beispiels vergleichen und analysieren, was mit den zugrundeliegenden Zeigern/Speicher/Assemblierung geschieht, um ein konkretes Verständnis dafür zu vermitteln, wie sie sich vergleichen.

Ich glaube, dass dies ein gutes Gespür dafür vermittelt, wie unterschiedlich diese Formen sind:

  • static_cast : macht einen Adress-Offset zur Laufzeit (geringe Laufzeitauswirkung) und keine Sicherheitsüberprüfung, ob ein Downcast korrekt ist.

  • dyanamic_cast : macht zur Laufzeit den gleichen Adressoffset wie static_cast aber auch eine teure Sicherheitsprüfung, ob ein Downcast mit RTTI korrekt ist.

    Mit dieser Sicherheitsprüfung können Sie abfragen, ob ein Basisklassenzeiger zur Laufzeit von einem bestimmten Typ ist, indem Sie eine Rückgabe von nullptr was auf einen ungültigen Downcast hinweist.

    Wenn Ihr Code also nicht in der Lage ist, das zu überprüfen nullptr und eine gültige Nicht-Abbruch-Aktion durchführen, sollten Sie einfach static_cast anstelle der dynamischen Besetzung.

    Wenn ein Abbruch die einzige Aktion ist, die Ihr Code ausführen kann, möchten Sie vielleicht nur die Option dynamic_cast in Debug-Builds ( -NDEBUG ), und verwenden Sie static_cast sonst z.B. wie hier geschehen , um Ihre schnellen Läufe nicht zu verlangsamen.

  • reinterpret_cast : macht zur Laufzeit nichts, nicht einmal den Adressoffset. Der Zeiger muss genau auf den richtigen Typ zeigen, nicht einmal eine Basisklasse funktioniert. Im Allgemeinen wollen Sie das nicht, es sei denn, es handelt sich um rohe Byte-Streams.

Betrachten Sie das folgende Codebeispiel:

main.cpp

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

Kompilieren, ausführen und disassemblieren mit:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

donde setarch es verwendet, um ASLR zu deaktivieren um Läufe besser vergleichen zu können.

Mögliche Ausgabe:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

Nun, wie erwähnt bei: https://en.wikipedia.org/wiki/Virtual_method_table um die virtuellen Methodenaufrufe effizient zu unterstützen, wobei angenommen wird, dass die Speicherdatenstrukturen von B1 von der Form sind:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

et B2 ist von der Form her:

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

dann Speicherdatenstruktur von D muss in etwa so aussehen:

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

Die wichtigste Tatsache ist, dass die Speicherdatenstruktur von D enthält in seinem Inneren eine Speicherstruktur, die mit der von B1 y B2 d.h.:

  • +0 sieht genauso aus wie ein B1, mit der B1 vtable für D, gefolgt von int_in_b1
  • +8 sieht genauso aus wie ein B2, mit der B2 vtable für D, gefolgt von int_in_b2

Deshalb kommen wir zu der kritischen Schlussfolgerung:

ein Upcast oder Downcast muss den Zeigerwert nur um einen zur Kompilierzeit bekannten Wert verschieben

Auf diese Weise kann, wenn D an das Array des Basistyps übergeben wird, berechnet der Typ-Cast tatsächlich diesen Offset und zeigt etwas, das genau wie ein gültiger B2 im Speicher, mit der Ausnahme, dass diese die vtable für D anstelle von B2 und daher funktionieren alle virtuellen Anrufe auf transparente Weise.

Z.B.:

b2s[1] = &d;

muss lediglich die Adresse von d + 8, um die entsprechende B2-ähnliche Datenstruktur zu erreichen.

Nun können wir uns endlich wieder dem Type Casting und der Analyse unseres konkreten Beispiels zuwenden.

In der stdout-Ausgabe sehen wir:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Daher ist die implizite static_cast Die dort vorgenommene Berechnung des Offsets von der vollen D Datenstruktur an 0x7fffffffc930 in die B2 wie zum Beispiel 0x7fffffffc940. Wir folgern auch, dass das, was zwischen 0x7fffffffc930 und 0x7fffffffc940 liegt, wahrscheinlich die B1 Daten und vtable.

Auf den nach unten gerichteten Abschnitten ist es nun einfach zu verstehen, wie und warum die ungültigen Abschnitte scheitern:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910 : Der Compiler hat gerade 0x10 Bytes zur Kompilierzeit erhöht, um von einer B2 zu dem enthaltenen D

    Aber weil b2s[0] war kein D verweist er nun auf einen undefinierten Speicherbereich.

    Die Demontage ist:

    49          dp = static_cast<D*>(b2s[0]);
       0x0000000000000fc8 <+414>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax
       0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>
       0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax
       0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>
       0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000000fe0 <+438>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    Wir sehen also, dass der GCC dies tut:

    • prüfen, ob der Zeiger NULL ist, und wenn ja, NULL zurückgeben
    • andernfalls subtrahieren Sie 0x10 davon, um den D die es nicht gibt
  • dynamic_cast<D*>(b2s[0]) 0 : C++ stellte fest, dass der Cast ungültig war und gab nullptr !

    Es gibt keine Möglichkeit, dies zur Kompilierzeit zu tun, und wir werden dies anhand der Disassemblierung bestätigen:

    59          dp = dynamic_cast<D*>(b2s[0]);
       0x00000000000010ec <+706>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax
       0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>
       0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx
       0x00000000000010fa <+720>:   48 8d 15 f7 0b 20 00    lea    0x200bf7(%rip),%rdx        # 0x201cf8 <_ZTI1D>
       0x0000000000001101 <+727>:   48 8d 35 28 0c 20 00    lea    0x200c28(%rip),%rsi        # 0x201d30 <_ZTI2B2>
       0x0000000000001108 <+734>:   48 89 c7        mov    %rax,%rdi
       0x000000000000110b <+737>:   e8 c0 fb ff ff  callq  0xcd0 <__dynamic_cast@plt>
       0x0000000000001110 <+742>:   eb 05   jmp    0x1117 <main()+749>
       0x0000000000001112 <+744>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000001117 <+749>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    Zuerst wird eine NULL-Prüfung durchgeführt, und es wird NULL zurückgegeben, wenn die Eingabe NULL ist.

    Andernfalls werden einige Argumente in RDX, RSI und RDI gesetzt und die __dynamic_cast .

    Ich habe jetzt nicht die Geduld, das weiter zu analysieren, aber wie andere schon sagten, kann das nur funktionieren, wenn __dynamic_cast um auf einige zusätzliche RTTI-Speicherdatenstrukturen zuzugreifen, die die Klassenhierarchie darstellen.

    Sie muss daher von der B2 Eintrag für diese Tabelle und durchläuft dann diese Klassenhierarchie, bis es feststellt, dass die vtable für eine D abgetippt b2s[0] .

    Aus diesem Grund ist der dynamische Guss potenziell teuer! Hier ist ein Beispiel, bei dem ein One-Liner-Patch eine dynamic_cast zu einer static_cast in einem komplexen Projekt die Laufzeit um 33% reduziert! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940 Dieser glaubt uns einfach blind: Wir haben gesagt, es gibt eine D unter der Adresse b2s[1] und der Compiler führt keine Offset-Berechnungen durch.

    Aber das ist falsch, denn D befindet sich tatsächlich an 0x7fffffffc930, was an 0x7fffffffc940 liegt, ist die B2-ähnliche Struktur innerhalb von D! Es wird also auf Müll zugegriffen.

    Wir können dies anhand der horrenden -O0 Baugruppe, die den Wert nur verschiebt:

    70          dp = reinterpret_cast<D*>(b2s[1]);
       0x00000000000011fa <+976>:   48 8b 45 d8     mov    -0x28(%rbp),%rax
       0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)

Verwandte Fragen:

Getestet auf Ubuntu 18.04 amd64, GCC 7.4.0.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X