Einige nützliche konkrete Beispiele für die Verwendung verschiedener Arten von Besetzungen finden Sie in der ersten Antwort auf eine ähnliche Frage in diesem anderen Thema .
TeaMonkie
2
Sie können oben wirklich gute Antworten auf Ihre Frage finden. Aber ich möchte hier noch einen Punkt anführen: @ e.James "Es gibt nichts, was diese neuen C ++ - Cast-Operatoren können und was C-Style-Cast nicht kann. Diese werden mehr oder weniger hinzugefügt, um die Lesbarkeit des Codes zu verbessern."
BreakBadSP
@BreakBadSP Die neuen Casts dienen nicht nur der besseren Lesbarkeit des Codes. Sie sind da, um es schwieriger zu machen, gefährliche Dinge zu tun, wie Konstante wegzuwerfen oder Zeiger anstelle ihrer Werte zu werfen. static_cast hat viel weniger Möglichkeiten, etwas Gefährliches zu tun als eine Besetzung im AC-Stil!
Zweiundvierzig
@ FourtyTwo stimmte zu
BreakBadSP
Antworten:
2570
static_castist die erste Besetzung, die Sie verwenden sollten. Es führt beispielsweise implizite Konvertierungen zwischen Typen aus (z. B. intto floatoder Zeiger auf void*) und kann auch explizite Konvertierungsfunktionen (oder implizite Konvertierungsfunktionen) aufrufen. In vielen Fällen ist eine explizite Angabe static_castnicht erforderlich, es ist jedoch wichtig zu beachten, dass die T(something)Syntax der Syntax entspricht (T)somethingund vermieden werden sollte (dazu später mehr). A T(something, something_else)ist jedoch sicher und ruft garantiert den Konstruktor auf.
static_castkann 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 virtualVererbung gewirkt wird. Es wird jedoch keine Überprüfung durchgeführt, und es ist ein undefiniertes Verhalten, static_casteine Hierarchie auf einen Typ herunterzufahren, der eigentlich nicht der Typ des Objekts ist.
const_castkann zum Entfernen oder Hinzufügen consteiner 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 constWerts nur dann undefiniert ist, wenn die ursprüngliche Variable lautet const. Wenn Sie damit consteinen 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 constzu einem Objekt verwendet werden, z. B. zum Aufrufen einer Elementfunktionsüberladung.
const_castfunktioniert auch ähnlich volatile, obwohl das weniger verbreitet ist.
dynamic_castwird 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_castsucht das gewünschte Objekt und gibt es wenn möglich zurück. Wenn dies nicht möglich ist, wird es nullptrim Fall eines Zeigers zurückgegeben oder std::bad_castim Fall einer Referenz geworfen .
dynamic_casthat jedoch einige Einschränkungen. Es funktioniert nicht, wenn die Vererbungshierarchie mehrere Objekte desselben Typs enthält (der sogenannte "gefürchtete Diamant") und Sie keine virtualVererbung verwenden. Es kann auch nur eine öffentliche Vererbung durchlaufen - es wird immer nicht durchlaufen protectedoder privatevererbt. Dies ist jedoch selten ein Problem, da solche Formen der Vererbung selten sind.
reinterpret_castist 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 intoder allen möglichen anderen bösen Dingen. Die einzige Garantie, die Sie erhalten, reinterpret_castist, 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_castdie 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)objectoder type(object)bzw. sind funktional äquivalent. Sie werden als die ersten der folgenden definiert, die erfolgreich sind:
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_castLetzteres sollte bevorzugt werden, wenn explizites Casting erforderlich ist, es sei denn, Sie sind sicher static_cast, dass es erfolgreich sein wird oder reinterpret_castfehlschlagen 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.
dynamic_cast ist nur für polymorphe Typen. Sie müssen es nur verwenden, wenn Sie in eine abgeleitete Klasse umwandeln. static_cast ist sicherlich die erste Option, es sei denn, Sie benötigen speziell die Funktionalität von dynamic_cast. Es ist im Allgemeinen keine wundersame Silberkugel "Typ-Checking Cast".
Jalf
2
Gute Antwort! Eine kurze Bemerkung: static_cast kann erforderlich sein, um die Hierarchie aufzurufen, falls Sie ein abgeleitetes * & haben, um es in Base * & umzuwandeln, da Doppelzeiger / Referenzen die Hierarchie nicht automatisch auflösen. Ich bin vor zwei Minuten auf eine solche (ehrlich gesagt nicht übliche) Situation gestoßen. ;-)
Bartgol
5
* "Keine andere C ++ - Besetzung kann const(nicht einmal reinterpret_cast) entfernen " ... wirklich? Was ist mit reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686
29
Ich denke, ein wichtiges Detail, das oben fehlt, ist, dass dynamic_cast im Vergleich zu static oder reinterpret_cast einen Laufzeitleistungsverlust aufweist. Dies ist wichtig, z. B. in Echtzeitsoftware.
jfritz42
5
Erwähnenswert reinterpret_castist möglicherweise, dass dies häufig die Waffe der Wahl ist, wenn es um die undurchsichtigen Datentypen einer API geht
Class Skeleton
333
Verwendung dynamic_castzum Konvertieren von Zeigern / Referenzen innerhalb einer Vererbungshierarchie.
Seien Sie vorsichtig mit dynamic_cast. Es basiert auf RTTI und dies funktioniert nicht wie erwartet über die Grenzen gemeinsam genutzter Bibliotheken hinweg. Nur weil Sie ausführbare und gemeinsam genutzte Bibliotheken 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 zum Überprüfen von Typen verwendet.
user3150128
198
(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 .
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);.....}
Die Theorie einiger anderer Antworten ist gut, aber immer noch verwirrend. Wenn Sie diese Beispiele nach dem Lesen der anderen Antworten sehen, sind sie alle wirklich sinnvoll. Ohne die Beispiele war ich mir immer noch nicht sicher, aber mit ihnen bin ich mir jetzt sicher, was die anderen Antworten bedeuten.
Solx
1
Über die letzte Verwendung von reinterpret_cast: Ist das nicht dasselbe wie die Verwendung static_cast<char*>(&val)?
Lorenzo Belli
3
@ LorenzoBelli Natürlich nicht. Hast Du es versucht? Letzteres ist in C ++ nicht gültig und blockiert die Kompilierung. static_castFunktioniert nur zwischen Typen mit definierten Konvertierungen, sichtbarer Beziehung durch Vererbung oder zu / von void *. Für alles andere gibt es andere Besetzungen. reinterpret castJeder 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.
underscore_d
2
reinterpret_cast ist ziemlich häufig, wenn Sie mit Systemsoftware wie Datenbanken arbeiten. In den meisten Fällen schreiben Sie Ihren eigenen Seitenmanager, der keine Ahnung hat, welcher Datentyp auf der Seite gespeichert ist, und nur einen ungültigen Zeiger zurückgibt. Es liegt an den höheren Ebenen, eine Besetzung neu zu interpretieren und daraus zu schließen, was sie wollen.
Sohaib
1
Das Beispiel const_cast zeigt undefiniertes Verhalten. Eine als const deklarierte Variable kann nicht dekonstiziert werden. Eine als Nicht-Konstante deklarierte Variable, die an eine Funktion übergeben wird, die eine Konstantenreferenz verwendet, kann in dieser Funktion dekonstituiert werden, ohne dass sie UB ist.
Johann Gerell
99
Es könnte hilfreich sein, wenn Sie ein wenig über Interna wissen ...
static_cast
Der C ++ - Compiler weiß bereits, wie man zwischen Skalertypen wie float nach int konvertiert. Verwenden Sie static_castfür sie.
Wenn Sie Compiler zu konvertieren von Typ fragen Azu B, static_castAnrufe B‚s vorbei Konstruktor Aals param. Alternativ Akönnte ein Konvertierungsoperator (dh A::operator B()) vorhanden sein. Wenn Sie Bkeinen solchen Konstruktor oder Akeinen Konvertierungsoperator haben, wird ein Fehler bei der Kompilierung angezeigt.
Die Umwandlung von A*bis ist B*immer erfolgreich, wenn A und B in der Vererbungshierarchie (oder ungültig) sind. Andernfalls wird ein Kompilierungsfehler angezeigt.
Gotcha : Wenn Sie werfen Basiszeiger auf abgeleitete Zeiger aber wenn tatsächliche Objekt Typ nicht wirklich abgeleitet dann Sie nicht bekommen Fehler. Sie erhalten einen schlechten Zeiger und sehr wahrscheinlich einen Segfault zur Laufzeit. Das Gleiche gilt für A&zu B&.
Gotcha : Cast von Derived to Base oder umgekehrt erstellt eine neue Kopie! Für Leute, die aus C # / Java kommen, kann dies eine große Überraschung sein, da das Ergebnis im Grunde ein abgeschnittenes Objekt ist, das aus Derived erstellt wurde.
dynamic_cast
dynamic_cast verwendet Laufzeittypinformationen, um herauszufinden, ob die Umwandlung gültig ist. Zum Beispiel kann (Base*)to (Derived*)fehlschlagen, wenn der Zeiger nicht vom abgeleiteten Typ ist.
Dies bedeutet, dass dynamic_cast im Vergleich zu static_cast sehr teuer ist!
Wenn A*für B*cast ungültig ist, gibt dynamic_cast nullptr zurück.
Wenn A&to B&ungültig ist, löst dynamic_cast eine bad_cast-Ausnahme aus.
Im Gegensatz zu anderen Casts entsteht ein Laufzeit-Overhead.
const_cast
Während static_cast nicht-const zu const ausführen kann, kann es nicht anders herum gehen. Der const_cast kann beides tun.
Ein Beispiel, bei dem dies nützlich ist, ist das Durchlaufen eines Containers, 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.
Ein weiteres Beispiel ist, wenn Sie ebenso implementieren möchten 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
Dies besagt im Grunde, dass diese Bytes an diesem Speicherort genommen und als gegebenes Objekt betrachtet werden.
Sie können beispielsweise 4 Byte Float auf 4 Byte Int laden, um zu sehen, wie Bits in Float aussehen.
Wenn die Daten für den Typ nicht korrekt sind, kann es natürlich zu Segfault kommen.
Für diese Besetzung gibt es keinen Laufzeitaufwand.
Ich habe die Informationen zum Konvertierungsoperator hinzugefügt, aber es gibt noch einige andere Dinge, die ebenfalls behoben werden sollten, und ich finde es nicht so angenehm, dies zu sehr zu aktualisieren. Elemente 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 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 mutablemöglicherweise die bessere Wahl, um logische Konstanz zu implementieren.
Adrian
1
@Adrian du bist in jeder Hinsicht korrekt. Die Antwort ist für Leute geschrieben, die mehr oder weniger Anfänger sind, und ich wollte sie nicht mit allen anderen Komplikationen überwältigen, die damit verbunden sind mutable, Cross Casting usw.
Ich habe es noch nie benutzt reinterpret_castund 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_castwird viel verwendet. Der Unterschied static_castbesteht darin, dass eine dynamic_castLaufzeitprüfung durchgeführt wird, die (sicherer) oder nicht (mehr Overhead) sein kann (siehe msdn ).
Ich habe reintrepret_cast für einen Zweck verwendet - das Herausholen der Bits aus einem Double (dieselbe Größe wie lang auf meiner Plattform).
Joshua
2
reinterpret_cast wird zB für die Arbeit mit COM-Objekten benötigt. CoCreateInstance () hat einen Ausgabeparameter vom Typ void ** (den letzten Parameter), in dem Sie Ihren als "INetFwPolicy2 * pNetFwPolicy2" deklarierten Zeiger übergeben. Dazu müssen Sie so etwas wie reinterpret_cast <void **> (& pNetFwPolicy2) schreiben.
Serge Rogatch
1
Vielleicht gibt es einen anderen Ansatz, aber ich reinterpret_castextrahiere 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); }
James Matta
Ich habe es nie benutzt reinterpret_cast, es gibt nicht sehr viele Verwendungszwecke dafür.
Pika der Zauberer der Wale
Persönlich habe ich nur reinterpret_castaus einem Grund verwendet gesehen. Ich habe gesehen, dass Rohobjektdaten, die in einem "Blob" -Datentyp in einer Datenbank gespeichert sind, reinterpret_castverwendet werden, um diese Rohdaten in das Objekt umzuwandeln, wenn die Daten aus der Datenbank abgerufen werden.
ImaginaryHuman072889
15
Zusätzlich zu den anderen bisherigen Antworten gibt es hier ein nicht offensichtliches Beispiel, bei dem dies static_castnicht ausreicht, reinterpret_castum 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 errorreinterpret_cast<void**>(&pNetFwPolicy2));
Funktioniert jedoch static_castfü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 :
Würde es nicht so funktionieren wie &static_cast<void*>(pNetFwPolicy2)statt static_cast<void**>(&pNetFwPolicy2)?
jp48
9
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) varund 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.
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 :
Verwenden Sie static_cast als Äquivalent zu einer Umwandlung im C-Stil, die eine Wertkonvertierung durchführt, oder wenn wir einen Zeiger explizit von einer Klasse in ihre Oberklasse hochwandeln müssen.
Verwenden Sie const_cast , um das const-Qualifikationsmerkmal zu entfernen.
Verwenden Sie reinterpret_cast , um unsichere Konvertierungen von Zeigertypen in und von ganzzahligen und anderen Zeigertypen durchzuführen. Verwenden Sie diese Option nur, wenn wir wissen, was wir tun, und die Aliasing-Probleme verstehen.
static_castvs dynamic_castvs reinterpret_castinternals 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 nullptreinen ungültigen Downcast anzeigt.
Wenn Ihr Code nicht in der Lage ist, dies zu überprüfen nullptrund eine gültige Nicht-Abbruch-Aktion auszuführen , sollten Sie daher nur die static_castdynamische Umwandlung verwenden.
Wenn ein Abbruch die einzige Aktion ist, die Ihr Code ausführen kann, möchten Sie möglicherweise nur die dynamic_castIn-Debug-Builds ( -NDEBUG) aktivieren und static_castanderweitig 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(){}virtualint f1(){return1;}int int_in_b1;};struct B2 {
B2(int int_in_b2): int_in_b2(int_in_b2){}virtual~B2(){}virtualint f2(){return2;}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(){return3;}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;}
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 Deine Speicherstruktur enthält, die mit der von B1und der von B2intern 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 Dberechnet die Typumwandlung beim Übergeben an das Basistyp-Array tatsächlich diesen Versatz und zeigt auf etwas, das genau wie eine B2im Speicher gültige aussieht :
b2s[1]=&d;
mit der Ausnahme, dass dieser die vtable für Danstelle von B2hat 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_castden Versatz von der vollständigen DDatenstruktur bei 0x7fffffffc930 zu der B2gleichen bei 0x7fffffffc940 korrekt berechnet . Wir schließen auch, dass zwischen 0x7fffffffc930 und 0x7fffffffc940 wahrscheinlich die B1Daten 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 B2in das enthaltene zu wechselnD
Aber weil b2s[0]es kein war D, zeigt es jetzt auf einen undefinierten Speicherbereich.
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 B2Eintrag für diese Tabelle ausgehen und dann diese Klassenhierarchie durchlaufen, bis festgestellt wird, dass die vtable für einen DTypecast von b2s[0].
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940Dieser glaubt uns nur blind: Wir sagten, es gibt eine Dat-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 -O0Versammlung bestätigen, die den Wert nur bewegt:
Antworten:
static_cast
ist die erste Besetzung, die Sie verwenden sollten. Es führt beispielsweise implizite Konvertierungen zwischen Typen aus (z. B.int
tofloat
oder Zeiger aufvoid*
) und kann auch explizite Konvertierungsfunktionen (oder implizite Konvertierungsfunktionen) aufrufen. In vielen Fällen ist eine explizite Angabestatic_cast
nicht erforderlich, es ist jedoch wichtig zu beachten, dass dieT(something)
Syntax der Syntax entspricht(T)something
und vermieden werden sollte (dazu später mehr). AT(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 durchvirtual
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ügenconst
einer Variablen verwendet werden; Kein anderer C ++ - Cast kann ihn entfernen (nicht einmalreinterpret_cast
). Es ist wichtig zu beachten, dass das Ändern eines früherenconst
Werts nur dann undefiniert ist, wenn die ursprüngliche Variable lautetconst
. Wenn Sie damitconst
einen Verweis auf etwas entfernen, mit dem nicht deklariert wurdeconst
, ist es sicher. Dies kann beispielsweise beim Überladen von Elementfunktionen hilfreich seinconst
. Es kann auch zum Hinzufügenconst
zu einem Objekt verwendet werden, z. B. zum Aufrufen einer Elementfunktionsüberladung.const_cast
funktioniert auch ähnlichvolatile
, 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. Derdynamic_cast
sucht das gewünschte Objekt und gibt es wenn möglich zurück. Wenn dies nicht möglich ist, wird esnullptr
im Fall eines Zeigers zurückgegeben oderstd::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 keinevirtual
Vererbung verwenden. Es kann auch nur eine öffentliche Vererbung durchlaufen - es wird immer nicht durchlaufenprotected
oderprivate
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 einemint
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
odertype(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) alsoconst_cast
reinterpret_cast
reinterpret_cast
, dannconst_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 sicherstatic_cast
, dass es erfolgreich sein wird oderreinterpret_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.quelle
const
(nicht einmalreinterpret_cast
) entfernen " ... wirklich? Was ist mitreinterpret_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 gehtVerwendung
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 entferntconst/volatile
. Vermeiden Sie dies, es sei denn, Sie verwenden eine const-falsche API.quelle
(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:
dynamic_cast:
const_cast:
reinterpret_cast:
quelle
static_cast<char*>(&val)
?static_cast
Funktioniert nur zwischen Typen mit definierten Konvertierungen, sichtbarer Beziehung durch Vererbung oder zu / vonvoid *
. Für alles andere gibt es andere Besetzungen.reinterpret cast
Jederchar *
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 zugelassenstatic_cast
.Es könnte hilfreich sein, wenn Sie ein wenig über Interna wissen ...
static_cast
static_cast
für sie.A
zuB
,static_cast
AnrufeB
‚s vorbei KonstruktorA
als param. AlternativA
könnte ein Konvertierungsoperator (dhA::operator B()
) vorhanden sein. Wenn SieB
keinen solchen Konstruktor oderA
keinen Konvertierungsoperator haben, wird ein Fehler bei der Kompilierung angezeigt.A*
bis istB*
immer erfolgreich, wenn A und B in der Vererbungshierarchie (oder ungültig) sind. Andernfalls wird ein Kompilierungsfehler angezeigt.A&
zuB&
.dynamic_cast
(Base*)
to(Derived*)
fehlschlagen, wenn der Zeiger nicht vom abgeleiteten Typ ist.A*
fürB*
cast ungültig ist, gibt dynamic_cast nullptr zurück.A&
toB&
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()
wieconst 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
quelle
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 istmutable
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 Unterschiedstatic_cast
besteht darin, dass einedynamic_cast
Laufzeitprüfung durchgeführt wird, die (sicherer) oder nicht (mehr Overhead) sein kann (siehe msdn ).quelle
reinterpret_cast
extrahiere Daten aus einem Array. Zum Beispiel, wenn ichchar*
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 istCoCreateInstance()
(siehe den letzten Parameter, der tatsächlich istvoid**
). 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 invoid**
mitstatic_cast
: Sie benötigenreinterpret_cast<void**>(&yourPointer)
.In Code:
Funktioniert jedoch
static_cast
für einfache Zeiger (keine Zeiger auf Zeiger), sodass der obige Code neu geschrieben werden kann, umreinterpret_cast
(zu einem Preis einer zusätzlichen Variablen) Folgendes zu vermeiden :quelle
&static_cast<void*>(pNetFwPolicy2)
stattstatic_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 solltenType(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.
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.
quelle
Um dies zu verstehen, betrachten wir das folgende Code-Snippet:
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 :
quelle
static_cast
vsdynamic_cast
vsreinterpret_cast
internals Ansicht auf einen Downcast / UpcastIn 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 wiestatic_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 diestatic_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 undstatic_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
Kompilieren, ausführen und zerlegen mit:
Hier
setarch
wird ASLR deaktiviert , um den Vergleich von Läufen zu vereinfachen.Mögliche Ausgabe:
Wie unter https://en.wikipedia.org/wiki/Virtual_method_table erwähnt , muss die Speicherdatenstruktur von
D
ungefähr so aussehen:Die Schlüsselfaktor ist, dass die Speicherdatenstruktur von
D
eine Speicherstruktur enthält, die mit der vonB1
und der vonB2
intern kompatibel ist .Daher kommen wir zu dem kritischen Schluss:
Auf diese Weise
D
berechnet die Typumwandlung beim Übergeben an das Basistyp-Array tatsächlich diesen Versatz und zeigt auf etwas, das genau wie eineB2
im Speicher gültige aussieht :mit der Ausnahme, dass dieser die vtable für
D
anstelle vonB2
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:
Daher hat das dort durchgeführte Implizit
static_cast
den Versatz von der vollständigenD
Datenstruktur bei 0x7fffffffc930 zu derB2
gleichen bei 0x7fffffffc940 korrekt berechnet . Wir schließen auch, dass zwischen 0x7fffffffc930 und 0x7fffffffc940 wahrscheinlich dieB1
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 aB2
in das enthaltene zu wechselnD
Aber weil
b2s[0]
es kein warD
, zeigt es jetzt auf einen undefinierten Speicherbereich.Die Demontage ist:
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 wurdenullptr
!Dies kann zum Zeitpunkt der Kompilierung auf keinen Fall durchgeführt werden, und wir werden dies bei der Demontage bestätigen:
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 einenD
Typecast vonb2s[0]
.Aus diesem Grund ist eine Neuinterpretation der Besetzung möglicherweise teuer! Hier ist ein Beispiel, in dem ein Einzeiler-Patch,
dynamic_cast
der astatic_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 eineD
at-Adresseb2s[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:Verwandte Fragen:
Getestet unter Ubuntu 18.04 amd64, GCC 7.4.0.
quelle