Was sind die richtigen Verwendungen von:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Besetzung im C-Stil
(type)value
- Besetzung im Funktionsstil
type(value)
Wie entscheidet man, welche in welchen konkreten Fällen verwendet werden soll?
Was sind die richtigen Verwendungen von:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Wie entscheidet man, welche in welchen konkreten Fällen verwendet werden soll?
Antworten:
static_cast
ist die erste Besetzung, die Sie verwenden sollten. Es führt beispielsweise implizite Konvertierungen zwischen Typen aus (z. B. int
to float
oder Zeiger auf void*
) und kann auch explizite Konvertierungsfunktionen (oder implizite Konvertierungsfunktionen) aufrufen. In vielen Fällen ist eine explizite Angabe static_cast
nicht erforderlich, es ist jedoch wichtig zu beachten, dass die T(something)
Syntax der Syntax entspricht (T)something
und vermieden werden sollte (dazu später mehr). A T(something, something_else)
ist jedoch sicher und ruft garantiert den Konstruktor auf.
static_cast
kann auch durch Vererbungshierarchien umgewandelt werden. Es ist nicht erforderlich, wenn nach oben (in Richtung einer Basisklasse) gewirkt wird, aber wenn nach unten gewirkt wird, kann es verwendet werden, solange es nicht durch virtual
Vererbung gewirkt wird. Es wird jedoch keine Überprüfung durchgeführt, und es ist ein undefiniertes Verhalten, static_cast
eine Hierarchie auf einen Typ herunterzufahren, der eigentlich nicht der Typ des Objekts ist.
const_cast
kann zum Entfernen oder Hinzufügen const
einer Variablen verwendet werden; Kein anderer C ++ - Cast kann ihn entfernen (nicht einmal reinterpret_cast
). Es ist wichtig zu beachten, dass das Ändern eines früheren const
Werts nur dann undefiniert ist, wenn die ursprüngliche Variable lautet const
. Wenn Sie damit const
einen Verweis auf etwas entfernen, mit dem nicht deklariert wurde const
, ist es sicher. Dies kann beispielsweise beim Überladen von Elementfunktionen hilfreich sein const
. Es kann auch zum Hinzufügen const
zu einem Objekt verwendet werden, z. B. zum Aufrufen einer Elementfunktionsüberladung.
const_cast
funktioniert auch ähnlich volatile
, obwohl das weniger verbreitet ist.
dynamic_cast
wird ausschließlich zur Behandlung von Polymorphismus verwendet. Sie können einen Zeiger oder Verweis auf einen beliebigen polymorphen Typ in einen anderen Klassentyp umwandeln (ein polymorpher Typ verfügt über mindestens eine deklarierte oder geerbte virtuelle Funktion). Sie können es für mehr als nur das Abwärtswerfen verwenden - Sie können es seitwärts oder sogar eine andere Kette hochwerfen. Der dynamic_cast
sucht das gewünschte Objekt und gibt es wenn möglich zurück. Wenn dies nicht möglich ist, wird es nullptr
im Fall eines Zeigers zurückgegeben oder std::bad_cast
im Fall einer Referenz geworfen .
dynamic_cast
hat jedoch einige Einschränkungen. Es funktioniert nicht, wenn die Vererbungshierarchie mehrere Objekte desselben Typs enthält (der sogenannte "gefürchtete Diamant") und Sie keine virtual
Vererbung verwenden. Es kann auch nur eine öffentliche Vererbung durchlaufen - es wird immer nicht durchlaufen protected
oder private
vererbt. Dies ist jedoch selten ein Problem, da solche Formen der Vererbung selten sind.
reinterpret_cast
ist die gefährlichste Besetzung und sollte sehr sparsam eingesetzt werden. Es verwandelt einen Typ direkt in einen anderen - z. B. das Umwandeln des Werts von einem Zeiger auf einen anderen oder das Speichern eines Zeigers in einem int
oder allen möglichen anderen bösen Dingen. Die einzige Garantie, die Sie erhalten, reinterpret_cast
ist, dass Sie normalerweise genau den gleichen Wert erhalten , wenn Sie das Ergebnis auf den ursprünglichen Typ zurücksetzen (jedoch nicht, wenn der Zwischentyp kleiner als der ursprüngliche Typ ist). Es gibt eine Reihe von Konvertierungen, reinterpret_cast
die ebenfalls nicht möglich sind. Es wird hauptsächlich für besonders seltsame Konvertierungen und Bitmanipulationen verwendet, z. B. zum Umwandeln eines Rohdatenstroms in tatsächliche Daten oder zum Speichern von Daten in den niedrigen Bits eines Zeigers auf ausgerichtete Daten.
Cast im C-Stil und Cast im Funktionsstil sind Casts mit (type)object
oder type(object)
bzw. sind funktional äquivalent. Sie werden als die ersten der folgenden definiert, die erfolgreich sind:
const_cast
static_cast
(obwohl Zugriffsbeschränkungen ignoriert werden)static_cast
(siehe oben) also const_cast
reinterpret_cast
reinterpret_cast
, dann const_cast
Es kann daher in einigen Fällen als Ersatz für andere Casts verwendet werden, kann jedoch aufgrund der Fähigkeit, sich in a zu verwandeln, äußerst gefährlich sein. reinterpret_cast
Letzteres sollte bevorzugt werden, wenn explizites Casting erforderlich ist, es sei denn, Sie sind sicher static_cast
, dass es erfolgreich sein wird oder reinterpret_cast
fehlschlagen wird . Berücksichtigen Sie auch dann die längere, explizitere Option.
Casts im C-Stil ignorieren auch die Zugriffskontrolle, wenn sie a ausführen static_cast
. Dies bedeutet, dass sie eine Operation ausführen können, die kein anderer Cast ausführen kann. Dies ist jedoch meistens ein Kludge, und meiner Meinung nach ist dies nur ein weiterer Grund, Casts im C-Stil zu vermeiden.
const
(nicht einmal reinterpret_cast
) entfernen " ... wirklich? Was ist mit reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
ist möglicherweise, dass dies häufig die Waffe der Wahl ist, wenn es um die undurchsichtigen Datentypen einer API geht
Verwendung dynamic_cast
zum Konvertieren von Zeigern / Referenzen innerhalb einer Vererbungshierarchie.
Verwendung static_cast
für normale Typkonvertierungen.
Verwendung reinterpret_cast
für die Neuinterpretation von Bitmustern auf niedriger Ebene. Mit äußerster Vorsicht verwenden.
Verwenden Sie const_cast
zum Gießen entfernt const/volatile
. Vermeiden Sie dies, es sei denn, Sie verwenden eine const-falsche API.
(Viele theoretische und konzeptionelle Erklärungen wurden oben gegeben)
Im Folgenden sind einige praktische Beispiele aufgeführt, als ich static_cast , dynamic_cast , const_cast , reinterpret_cast verwendet habe .
(Verweist auch darauf, um die Erklärung 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);
}
static_cast<char*>(&val)
?
static_cast
Funktioniert nur zwischen Typen mit definierten Konvertierungen, sichtbarer Beziehung durch Vererbung oder zu / von void *
. Für alles andere gibt es andere Besetzungen. reinterpret cast
Jeder char *
Typ darf das Lesen der Darstellung eines Objekts ermöglichen - und einer der wenigen Fälle, in denen dieses Schlüsselwort nützlich ist, und kein zügelloser Generator für implementierungs- / undefiniertes Verhalten. Dies wird jedoch nicht als "normale" Konvertierung angesehen und wird daher von den (normalerweise) sehr konservativen Personen nicht zugelassen static_cast
.
Es könnte hilfreich sein, wenn Sie ein wenig über Interna wissen ...
static_cast
static_cast
für sie.A
zu B
, static_cast
Anrufe B
‚s vorbei Konstruktor A
als param. Alternativ A
könnte ein Konvertierungsoperator (dh A::operator B()
) vorhanden sein. Wenn Sie B
keinen solchen Konstruktor oder A
keinen Konvertierungsoperator haben, wird ein Fehler bei der Kompilierung angezeigt.A*
bis ist B*
immer erfolgreich, wenn A und B in der Vererbungshierarchie (oder ungültig) sind. Andernfalls wird ein Kompilierungsfehler angezeigt.A&
zu B&
.dynamic_cast
(Base*)
to (Derived*)
fehlschlagen, wenn der Zeiger nicht vom abgeleiteten Typ ist.A*
für B*
cast ungültig ist, gibt dynamic_cast nullptr zurück.A&
to B&
ungültig ist, löst dynamic_cast eine bad_cast-Ausnahme aus.const_cast
set<T>
der nur seine Elemente als const zurückgibt, um sicherzustellen, dass Sie seinen Schlüssel nicht ändern. Wenn Sie jedoch beabsichtigen, die Nicht-Schlüsselelemente des Objekts zu ändern, sollte dies in Ordnung sein. Sie können const_cast verwenden, um constness zu entfernen.T& SomeClass::foo()
wie const T& SomeClass::foo() const
. Um Codeduplizierungen zu vermeiden, können Sie const_cast anwenden, um den Wert einer Funktion von einer anderen zurückzugeben.reinterpret_cast
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 zur Laufzeit zu einem Segfault führen kann, wenn Sie Glück haben. 2. Dynamische Abgüsse können auch beim Cross Casting verwendet werden. 3. Const Casts können in einigen Fällen zu UB führen. Die Verwendung ist mutable
möglicherweise die bessere Wahl, um logische Konstanz zu implementieren.
mutable
, Cross Casting usw.
Ist dies Ihre Frage beantwortet?
Ich habe es noch nie benutzt reinterpret_cast
und frage mich, ob es nicht nach schlechtem Design riecht, wenn ich auf ein Gehäuse stoße, das es braucht. In der Codebasis, an der ich arbeite, dynamic_cast
wird viel verwendet. Der Unterschied static_cast
besteht darin, dass eine dynamic_cast
Laufzeitprüfung durchgeführt wird, die (sicherer) oder nicht (mehr Overhead) sein kann (siehe msdn ).
reinterpret_cast
extrahiere Daten aus einem Array. Zum Beispiel, wenn ich char*
einen großen Puffer voller gepackter Binärdaten habe, den ich durchlaufen muss, um einzelne Grundelemente unterschiedlichen Typs zu erhalten. So etwas wie das:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, es gibt nicht sehr viele Verwendungszwecke dafür.
reinterpret_cast
aus einem Grund verwendet gesehen. Ich habe gesehen, dass Rohobjektdaten, die in einem "Blob" -Datentyp in einer Datenbank gespeichert sind, reinterpret_cast
verwendet werden, um diese Rohdaten in das Objekt umzuwandeln, wenn die Daten aus der Datenbank abgerufen werden.
Zusätzlich zu den anderen bisherigen Antworten gibt es hier ein nicht offensichtliches Beispiel, bei dem dies static_cast
nicht ausreicht, reinterpret_cast
um benötigt zu werden. Angenommen, es gibt eine Funktion, die in einem Ausgabeparameter Zeiger auf Objekte verschiedener Klassen zurückgibt (die keine gemeinsame Basisklasse haben). Ein reales Beispiel für eine solche Funktion ist CoCreateInstance()
(siehe den letzten Parameter, der tatsächlich ist void**
). Angenommen, Sie fordern von dieser Funktion eine bestimmte Objektklasse an, damit Sie den Typ für den Zeiger im Voraus kennen (was Sie häufig für COM-Objekte tun). In diesem Fall können Sie keinen Zeiger auf Ihren Zeiger in void**
mit static_cast
: Sie benötigen reinterpret_cast<void**>(&yourPointer)
.
In Code:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
Funktioniert jedoch static_cast
für einfache Zeiger (keine Zeiger auf Zeiger), sodass der obige Code neu geschrieben werden kann, um reinterpret_cast
(zu einem Preis einer zusätzlichen Variablen) Folgendes zu vermeiden :
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)
statt static_cast<void**>(&pNetFwPolicy2)
?
Während andere Antworten alle Unterschiede zwischen C ++ - Casts gut beschrieben haben, möchte ich einen kurzen Hinweis hinzufügen, warum Sie keine C-Casts (Type) var
und verwenden sollten Type(var)
.
Für C ++ - Anfänger scheinen C-ähnliche Casts die Superset-Operation gegenüber C ++ - Casts zu sein (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()), und jemand könnte sie den C ++ - Casts vorziehen . Tatsächlich ist die Besetzung im C-Stil die Obermenge und kürzer zu schreiben.
Das Hauptproblem von Casts im C-Stil besteht darin, dass sie die wahre Absicht des Entwicklers verbergen. Die Casts im C-Stil können praktisch alle Arten von Castings ausführen, von normalerweise sicheren Casts mit static_cast <> () und dynamic_cast <> () bis zu potenziell gefährlichen Casts wie const_cast <> (), bei denen der const-Modifikator entfernt werden kann, damit die const-Variablen entfernt werden kann geändert und neu interpretiert werden_cast <> (), wodurch sogar ganzzahlige Werte in Zeiger neu interpretiert werden können.
Hier ist das Beispiel.
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
Der Hauptgrund, warum der Sprache C ++ - Casts hinzugefügt wurden, bestand darin, einem Entwickler zu ermöglichen, seine Absichten zu klären - warum er diese Casting durchführen wird. Durch die Verwendung von C-Casts, die in C ++ perfekt gültig sind, wird Ihr Code weniger lesbar und fehleranfälliger, insbesondere für andere Entwickler, die Ihren Code nicht erstellt haben. Um Ihren Code lesbarer und expliziter zu machen, sollten Sie immer C ++ - Casts C-Casts vorziehen.
Hier ist ein kurzes Zitat aus Bjarne Stroustrups (dem Autor von C ++) Buch The C ++ Programming Language 4th Edition - Seite 302.
Diese Besetzung im C-Stil ist weitaus gefährlicher als die genannten Konvertierungsoperatoren, da die Notation in einem großen Programm schwerer zu erkennen ist und die vom Programmierer beabsichtigte Art der Konvertierung nicht explizit ist.
Um dies zu verstehen, betrachten wir das folgende Code-Snippet:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
Nur Zeile (4) wird fehlerfrei kompiliert. Nur reinterpret_cast kann verwendet werden, um einen Zeiger auf ein Objekt in einen Zeiger auf einen nicht verwandten Objekttyp zu konvertieren.
Dies ist zu beachten: Der dynamic_cast schlägt zur Laufzeit fehl. Bei den meisten Compilern kann er jedoch auch nicht kompiliert werden, da die Struktur des gecasteten Zeigers keine virtuellen Funktionen enthält. Dies bedeutet, dass dynamic_cast nur mit polymorphen Klassenzeigern funktioniert .
Wenn C ++ Guss zu verwenden :
static_cast
vs dynamic_cast
vs reinterpret_cast
internals Ansicht auf einen Downcast / Upcast
In dieser Antwort möchte ich diese drei Mechanismen anhand eines konkreten Upcast / Downcast-Beispiels vergleichen und analysieren, was mit den zugrunde liegenden Zeigern / Speicher / Assembly geschieht, um ein konkretes Verständnis für deren Vergleich zu erhalten.
Ich glaube, dass dies eine gute Vorstellung davon geben wird, wie unterschiedlich diese Darsteller sind:
static_cast
: Versetzt eine Adresse zur Laufzeit (geringe Auswirkungen auf die Laufzeit) und überprüft nicht, ob ein Downcast korrekt ist.
dyanamic_cast
: macht zur Laufzeit den gleichen Adressoffset wie static_cast
, aber auch und eine teure Sicherheitsüberprüfung, ob ein Downcast mit RTTI korrekt ist.
Mit dieser Sicherheitsüberprüfung können Sie abfragen, ob ein Basisklassenzeiger zur Laufzeit von einem bestimmten Typ ist, indem Sie überprüfen, ob eine Rückgabe nullptr
einen ungültigen Downcast anzeigt.
Wenn Ihr Code nicht in der Lage ist, dies zu überprüfen nullptr
und eine gültige Nicht-Abbruch-Aktion auszuführen , sollten Sie daher nur die static_cast
dynamische Umwandlung verwenden.
Wenn ein Abbruch die einzige Aktion ist, die Ihr Code ausführen kann, möchten Sie möglicherweise nur die dynamic_cast
In-Debug-Builds ( -NDEBUG
) aktivieren und static_cast
anderweitig verwenden, z. B. wie hier ausgeführt , 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. Sie möchten dies im Allgemeinen nur, wenn Rohbyte-Streams beteiligt sind.
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 zerlegen 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
Hier setarch
wird ASLR deaktiviert , um den Vergleich von Läufen zu vereinfachen.
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
Wie unter https://en.wikipedia.org/wiki/Virtual_method_table erwähnt , muss die Speicherdatenstruktur von D
ungefähr so aussehen:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
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 Schlüsselfaktor ist, dass die Speicherdatenstruktur von D
eine Speicherstruktur enthält, die mit der von B1
und der von B2
intern kompatibel ist .
Daher kommen wir zu dem kritischen Schluss:
Ein Upcast oder Downcast muss nur den Zeigerwert um einen Wert verschieben, der zur Kompilierungszeit bekannt ist
Auf diese Weise D
berechnet die Typumwandlung beim Übergeben an das Basistyp-Array tatsächlich diesen Versatz und zeigt auf etwas, das genau wie eine B2
im Speicher gültige aussieht :
b2s[1] = &d;
mit der Ausnahme, dass dieser die vtable für D
anstelle von B2
hat und daher alle virtuellen Aufrufe transparent funktionieren.
Jetzt können wir endlich zum Typguss und zur Analyse unseres konkreten Beispiels zurückkehren.
Aus der Standardausgabe sehen wir:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Daher hat das dort durchgeführte Implizit static_cast
den Versatz von der vollständigen D
Datenstruktur bei 0x7fffffffc930 zu der B2
gleichen bei 0x7fffffffc940 korrekt berechnet . Wir schließen auch, dass zwischen 0x7fffffffc930 und 0x7fffffffc940 wahrscheinlich die B1
Daten und die vtable liegen.
In den niedergeschlagenen Abschnitten ist es jetzt leicht zu verstehen, wie die ungültigen fehlschlagen und warum:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: Der Compiler ist gerade zur Kompilierungszeit um 0x10 gestiegen, um zu versuchen, von a B2
in das enthaltene zu wechselnD
Aber weil b2s[0]
es kein war D
, zeigt es jetzt 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 GCC Folgendes tut:
D
was nicht existiertdynamic_cast<D*>(b2s[0]) 0
: C ++ hat tatsächlich festgestellt, dass die Besetzung ungültig ist und zurückgegeben wurde nullptr
!
Dies kann zum Zeitpunkt der Kompilierung auf keinen Fall durchgeführt werden, und wir werden dies bei der Demontage 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 gibt es eine NULL-Prüfung, die NULL zurückgibt, wenn die Eingabe NULL ist.
Andernfalls werden einige Argumente in RDX, RSI und RDI eingerichtet und aufgerufen __dynamic_cast
.
Ich habe nicht die Geduld, dies jetzt weiter zu analysieren, aber wie andere sagten, besteht die einzige Möglichkeit, dies zu erreichen, darin __dynamic_cast
, auf einige zusätzliche speicherinterne RTTI-Datenstrukturen zuzugreifen, die die Klassenhierarchie darstellen.
Es muss daher vom B2
Eintrag für diese Tabelle ausgehen und dann diese Klassenhierarchie durchlaufen, bis festgestellt wird, dass die vtable für einen D
Typecast von b2s[0]
.
Aus diesem Grund ist eine Neuinterpretation der Besetzung möglicherweise teuer! Hier ist ein Beispiel, in dem ein Einzeiler-Patch, dynamic_cast
der a static_cast
in ein komplexes Projekt konvertiert , die Laufzeit um 33% reduzierte! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
Dieser glaubt uns nur blind: Wir sagten, es gibt eine D
at-Adresse b2s[1]
, und der Compiler führt keine Offset-Berechnungen durch.
Dies ist jedoch falsch, da D tatsächlich bei 0x7fffffffc930 liegt. Was bei 0x7fffffffc940 liegt, ist die B2-ähnliche Struktur in D! So wird auf Müll zugegriffen.
Wir können dies anhand der schrecklichen -O0
Versammlung bestätigen, die den Wert nur bewegt:
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 unter Ubuntu 18.04 amd64, GCC 7.4.0.