Quelles sont les utilisations appropriées de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Fonte de style C
(type)value
- Fonte de style fonctionnel
type(value)
Comment décider lequel utiliser dans quels cas spécifiques?
Quelles sont les utilisations appropriées de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Comment décider lequel utiliser dans quels cas spécifiques?
Réponses:
static_cast
est la première distribution que vous devriez essayer d'utiliser. Il fait des choses comme des conversions implicites entre les types (comme int
vers float
ou pointeur vers void*
), et il peut également appeler des fonctions de conversion explicites (ou implicites). Dans de nombreux cas, une explication explicite static_cast
n'est pas nécessaire, mais il est important de noter que la T(something)
syntaxe est équivalente (T)something
et doit être évitée (plus à ce sujet plus tard). A T(something, something_else)
est cependant sûr et garanti d'appeler le constructeur.
static_cast
peut également transtyper les hiérarchies d'héritage. Elle n'est pas nécessaire lors d'une conversion vers le haut (vers une classe de base), mais lors d'une conversion vers le bas, elle peut être utilisée tant qu'elle ne transite pas par virtual
héritage. Il ne fait pas de vérification, cependant, et c'est un comportement indéfini de static_cast
descendre une hiérarchie vers un type qui n'est pas réellement le type de l'objet.
const_cast
peut être utilisé pour supprimer ou ajouter const
à une variable; aucun autre cast C ++ n'est capable de le supprimer (pas même reinterpret_cast
). Il est important de noter que la modification d'une ancienne const
valeur n'est indéfinie que si la variable d'origine l'est const
; si vous l'utilisez pour const
enlever une référence à quelque chose qui n'a pas été déclaré const
, c'est sûr. Cela peut être utile lors de la surcharge de fonctions membres basées sur const
, par exemple. Il peut également être utilisé pour ajouter const
à un objet, par exemple pour appeler une surcharge de fonction membre.
const_cast
fonctionne également de la même manière volatile
, bien que ce soit moins courant.
dynamic_cast
est exclusivement utilisé pour gérer le polymorphisme. Vous pouvez convertir un pointeur ou une référence à tout type polymorphe en tout autre type de classe (un type polymorphe a au moins une fonction virtuelle, déclarée ou héritée). Vous pouvez l'utiliser pour plus que simplement lancer vers le bas - vous pouvez lancer sur le côté ou même sur une autre chaîne. Le dynamic_cast
cherchera l'objet souhaité et le restituera si possible. Si ce n'est pas le cas, il reviendra nullptr
dans le cas d'un pointeur, ou sera renvoyé std::bad_cast
dans le cas d'une référence.
dynamic_cast
a cependant quelques limites. Cela ne fonctionne pas s'il y a plusieurs objets du même type dans la hiérarchie d'héritage (le soi-disant «diamant redouté») et que vous n'utilisez pas l' virtual
héritage. Il ne peut également passer que par héritage public - il échouera toujours à traverser protected
ou private
hériter. Il s'agit cependant rarement d'un problème, car de telles formes d'héritage sont rares.
reinterpret_cast
est le plâtre le plus dangereux et doit être utilisé avec parcimonie. Il transforme un type directement en un autre - comme la conversion de la valeur d'un pointeur en un autre, ou le stockage d'un pointeur dans une int
, ou toutes sortes d'autres choses désagréables. Généralement, la seule garantie que vous obtenez reinterpret_cast
est que, normalement, si vous restituez le résultat au type d'origine, vous obtiendrez la même valeur exacte (mais pas si le type intermédiaire est plus petit que le type d'origine). Il existe également un certain nombre de conversions qui reinterpret_cast
ne peuvent pas le faire. Il est principalement utilisé pour des conversions et des manipulations de bits particulièrement étranges, comme transformer un flux de données brutes en données réelles ou stocker des données dans les bits faibles d'un pointeur vers des données alignées.
Les castes de style C et les castes de style fonctionnel sont des castes utilisant (type)object
ou type(object)
, respectivement, et sont fonctionnellement équivalentes. Ils sont définis comme le premier des éléments suivants qui réussit:
const_cast
static_cast
(mais en ignorant les restrictions d'accès)static_cast
(voir ci-dessus), puis const_cast
reinterpret_cast
reinterpret_cast
, puis const_cast
Il peut donc être utilisé en remplacement d'autres lancers dans certains cas, mais peut être extrêmement dangereux en raison de la capacité de se transformer en un reinterpret_cast
, et ce dernier devrait être préféré lorsqu'une conversion explicite est nécessaire, sauf si vous êtes sûr de static_cast
réussir ou d' reinterpret_cast
échouer. . Même alors, considérez l'option plus longue et plus explicite.
Les conversions de style C ignorent également le contrôle d'accès lors de l'exécution d'un static_cast
, ce qui signifie qu'elles ont la possibilité d'effectuer une opération qu'aucune autre distribution ne peut faire. C'est surtout un coup de coude, cependant, et dans mon esprit, c'est juste une autre raison d'éviter les moulages de style C.
const
(même pas reinterpret_cast
)" ... vraiment? Et alors reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
c'est souvent l'arme de choix lorsqu'il s'agit d'un ensemble de types de données opaques pour une API
À utiliser dynamic_cast
pour convertir des pointeurs / références dans une hiérarchie d'héritage.
À utiliser static_cast
pour les conversions de type ordinaire.
À utiliser reinterpret_cast
pour la réinterprétation de bas niveau des modèles de bits. À utiliser avec une extrême prudence.
Utilisez const_cast
pour jeter const/volatile
. Évitez cela, sauf si vous êtes bloqué en utilisant une API const-incorrecte.
(Beaucoup d'explications théoriques et conceptuelles ont été données ci-dessus)
Voici quelques - uns des exemples pratiques quand je static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Se réfère également à cela pour comprendre l'explication: 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
ne fonctionne qu'entre types avec des conversions définies, relation visible par héritage ou vers / depuis void *
. Pour tout le reste, il existe d'autres modèles. reinterpret cast
à n'importe quel char *
type est autorisé pour permettre la lecture de la représentation de n'importe quel objet - et l'un des seuls cas où ce mot-clé est utile, pas un générateur rampant de mise en œuvre / comportement indéfini. Mais cela n'est pas considéré comme une conversion «normale», ce qui n'est pas autorisé par les (généralement) très conservateurs static_cast
.
Cela pourrait aider si vous connaissez peu les éléments internes ...
static_cast
static_cast
pour eux.A
en B
, le constructeur d' static_cast
appels B
passe A
en paramètre. Alternativement, A
pourrait avoir un opérateur de conversion (ie A::operator B()
). Si B
n'a pas un tel constructeur ou A
n'a pas d'opérateur de conversion, vous obtenez une erreur de temps de compilation.A*
à B*
réussit toujours si A et B sont dans la hiérarchie d'héritage (ou nuls) sinon vous obtenez une erreur de compilation.A&
la B&
.dynamic_cast
(Base*)
to (Derived*)
peut échouer si le pointeur n'est pas réellement de type dérivé.A*
to B*
, si cast n'est pas valide, dynamic_cast renverra nullptr.A&
de B&
si coulée est invalide, dynamic_cast jetteront exception bad_cast.const_cast
set<T>
qui ne renvoie que ses éléments sous forme de const pour vous assurer que vous ne changez pas sa clé. Cependant, si votre intention est de modifier les membres non clés de l'objet, cela devrait être correct. Vous pouvez utiliser const_cast pour supprimer la constance.T& SomeClass::foo()
ainsi que const T& SomeClass::foo() const
. Pour éviter la duplication de code, vous pouvez appliquer const_cast pour renvoyer la valeur d'une fonction d'une autre.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.
Vous obtenez UB qui peut entraîner un défaut de segmentation lors de l'exécution si vous êtes chanceux. 2. Les moulages dynamiques peuvent également être utilisés pour la coulée croisée. 3. Dans certains cas, les lancers const peuvent entraîner une UB. L'utilisation mutable
peut être un meilleur choix pour implémenter la constance logique.
mutable
Est- ce que cela répond à votre question?
Je ne l'ai jamais utilisé reinterpret_cast
et je me demande si le fait de rencontrer un boîtier qui en a besoin n'est pas une odeur de mauvaise conception. Dans la base de code sur laquelle je travaille dynamic_cast
est beaucoup utilisé. La différence avec static_cast
est qu'une dynamic_cast
vérification de l'exécution ne peut (plus sûre) ou pas (plus de surcharge) être ce que vous voulez (voir msdn ).
reinterpret_cast
pour extraire des éléments de données d'un tableau. Par exemple, si j'ai un char*
contenant un grand tampon plein de données binaires compressées que je dois parcourir et obtenir des primitives individuelles de types différents. Quelque chose comme ça: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
, il n'y a pas beaucoup d'utilisations.
reinterpret_cast
utilisé que pour une seule raison. J'ai vu des données d'objet brutes stockées dans un type de données "blob" dans une base de données, puis lorsque les données sont extraites de la base de données, elles reinterpret_cast
sont utilisées pour transformer ces données brutes en objet.
En plus des autres réponses jusqu'à présent, voici un exemple non évident où ce static_cast
n'est pas suffisant, ce qui reinterpret_cast
est nécessaire. Supposons qu'il existe une fonction qui, dans un paramètre de sortie, renvoie des pointeurs vers des objets de classes différentes (qui ne partagent pas une classe de base commune). Un exemple réel d'une telle fonction est CoCreateInstance()
(voir le dernier paramètre, qui est en fait void**
). Supposons que vous demandiez une classe d'objet particulière à cette fonction, afin de connaître à l'avance le type du pointeur (ce que vous faites souvent pour les objets COM). Dans ce cas, vous ne pouvez pas convertir le pointeur vers votre pointeur void**
avec static_cast
: vous en avez besoin reinterpret_cast<void**>(&yourPointer)
.
Dans du 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) );
Cependant, static_cast
fonctionne pour des pointeurs simples (pas des pointeurs vers des pointeurs), donc le code ci-dessus peut être réécrit pour éviter reinterpret_cast
(au prix d'une variable supplémentaire) de la manière suivante:
#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)
au lieu de static_cast<void**>(&pNetFwPolicy2)
?
Alors que d'autres réponses décrivaient bien toutes les différences entre les conversions C ++, je voudrais ajouter une courte note pourquoi vous ne devriez pas utiliser les conversions de style C (Type) var
et Type(var)
.
Pour les débutants C ++, les conversions de style C ressemblent à l'opération de sur-ensemble par rapport aux conversions C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) et quelqu'un pourrait les préférer aux conversions C ++ . En fait, le casting de style C est le sur-ensemble et le plus court à écrire.
Le principal problème des castes de style C est qu'elles cachent l'intention réelle du développeur de la distribution. Les transtypages de style C peuvent effectuer pratiquement tous les types de transtypage, des transtypages normalement sécurisés effectués par static_cast <> () et dynamic_cast <> () aux transtypages potentiellement dangereux comme const_cast <> (), où le modificateur const peut être supprimé de sorte que les variables const peut être modifié et reinterpret_cast <> () qui peut même réinterpréter des valeurs entières vers des pointeurs.
Voici l'exemple.
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.
La raison principale pour laquelle les castings C ++ ont été ajoutés au langage était de permettre à un développeur de clarifier ses intentions - pourquoi il va faire ce cast. En utilisant des conversions de style C qui sont parfaitement valides en C ++, vous rendez votre code moins lisible et plus sujet aux erreurs, en particulier pour les autres développeurs qui n'ont pas créé votre code. Donc, pour rendre votre code plus lisible et explicite, vous devriez toujours préférer les transtypages C ++ aux transtypages de style C.
Voici une courte citation du livre de Bjarne Stroustrup (l'auteur de C ++) The C ++ Programming Language 4th edition - page 302.
Cette distribution de style C est beaucoup plus dangereuse que les opérateurs de conversion nommés car la notation est plus difficile à repérer dans un grand programme et le type de conversion prévu par le programmeur n'est pas explicite.
Pour comprendre, considérons l'extrait de code ci-dessous:
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;
}
Seule la ligne (4) se compile sans erreur. Seul reinterpret_cast peut être utilisé pour convertir un pointeur vers un objet en un pointeur vers n'importe quel type d'objet sans rapport.
Une chose à noter est la suivante: le dynamic_cast échouerait au moment de l'exécution, mais sur la plupart des compilateurs, il ne parviendra pas non plus à compiler car il n'y a pas de fonctions virtuelles dans la structure du pointeur en cours de conversion, ce qui signifie que dynamic_cast ne fonctionnera qu'avec des pointeurs de classe polymorphes. .
Quand utiliser le cast C ++ :
static_cast
vs dynamic_cast
vs reinterpret_cast
internals voir sur un downcast / upcast
Dans cette réponse, je veux comparer ces trois mécanismes sur un exemple concret upcast / downcast et analyser ce qui arrive aux pointeurs / mémoire / assemblage sous-jacents pour donner une compréhension concrète de la façon dont ils se comparent.
Je crois que cela donnera une bonne intuition sur la façon dont ces lancers sont différents:
static_cast
: une adresse est-elle décalée lors de l'exécution (faible impact sur l'exécution) et aucune vérification de la sécurité de la correction d'un downcast n'est correcte.
dyanamic_cast
: fait le même décalage d'adresse lors de l'exécution comme static_cast
, mais aussi et une vérification de sécurité coûteuse qu'un downcast est correct en utilisant RTTI.
Cette vérification de sécurité vous permet de rechercher si un pointeur de classe de base est d'un type donné au moment de l'exécution en vérifiant un retour nullptr
qui indique un abaissement invalide.
Par conséquent, si votre code n'est pas en mesure de vérifier cela nullptr
et de prendre une action non abandonnée valide, vous devez simplement utiliser à la static_cast
place de la conversion dynamique.
Si un abandon est la seule action que votre code peut entreprendre, peut-être voulez-vous uniquement activer les dynamic_cast
builds de débogage ( -NDEBUG
), et utiliser static_cast
autrement, par exemple comme ici , pour ne pas ralentir vos exécutions rapides.
reinterpret_cast
: ne fait rien à l'exécution, pas même le décalage d'adresse. Le pointeur doit pointer exactement vers le type correct, même une classe de base ne fonctionne pas. Vous ne voulez généralement pas cela à moins que des flux d'octets bruts ne soient impliqués.
Prenons l'exemple de code suivant:
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;
}
Compilez, exécutez et démontez avec:
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
où setarch
est utilisé pour désactiver ASLR pour faciliter la comparaison des exécutions.
Sortie possible:
&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
Maintenant, comme mentionné sur: https://en.wikipedia.org/wiki/Virtual_method_table afin de prendre en charge efficacement les appels de méthode virtuelle, la structure des données de la mémoire de D
doit ressembler à:
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
Le fait essentiel est que la structure de données de mémoire de D
contient à l'intérieur une structure de mémoire compatible avec celle de B1
et celle de l' B2
intérieur.
Par conséquent, nous arrivons à la conclusion critique:
un upcast ou downcast n'a besoin que de déplacer la valeur du pointeur par une valeur connue au moment de la compilation
De cette façon, lorsqu'il D
est passé au tableau de types de base, le transtypage de type calcule réellement ce décalage et pointe quelque chose qui ressemble exactement à un valide B2
en mémoire:
b2s[1] = &d;
sauf que celui-ci a la vtable pour D
au lieu de B2
, et donc tous les appels virtuels fonctionnent de manière transparente.
Maintenant, nous pouvons enfin revenir au type de coulée et à l'analyse de notre exemple concret.
De la sortie standard, nous voyons:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Par conséquent, l'implicite static_cast
fait là a correctement calculé le décalage entre la D
structure de données complète à 0x7fffffffc930 et celle B2
similaire qui est à 0x7fffffffc940. Nous déduisons également que ce qui se situe entre 0x7fffffffc930 et 0x7fffffffc940 est probablement les B1
données et la table.
Ensuite, dans les sections downcast, il est maintenant facile de comprendre comment les invalides échouent et pourquoi:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: le compilateur vient de monter 0x10 lors de la compilation des octets pour essayer de passer de a B2
à contenantD
Mais comme ce b2s[0]
n'était pas le cas D
, il pointe désormais vers une région de mémoire non définie.
Le démontage est:
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)
nous voyons donc que GCC fait:
D
qui n'existe pasdynamic_cast<D*>(b2s[0]) 0
: C ++ a effectivement trouvé que la distribution n'était pas valide et est retournée nullptr
!
Il n'y a aucun moyen que cela puisse être fait au moment de la compilation, et nous confirmerons que dès le démontage:
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)
Il y a d'abord une vérification NULL, et elle retourne NULL si l'entrée est NULL.
Sinon, il configure certains arguments dans RDX, RSI et RDI et appelle __dynamic_cast
.
Je n'ai pas la patience d'analyser cela plus en détail maintenant, mais comme d'autres l'ont dit, la seule façon pour que cela fonctionne est __dynamic_cast
d'accéder à des structures de données en mémoire RTTI supplémentaires qui représentent la hiérarchie des classes.
Il faut donc commencer à partir de l' B2
entrée de cette table, puis marcher cette hiérarchie de classe jusqu'à ce qu'il trouve que le vtable pour un D
typecast de b2s[0]
.
C'est pourquoi réinterpréter le casting est potentiellement cher! Voici un exemple où un patch à une ligne convertissant un dynamic_cast
en un static_cast
dans un projet complexe a réduit le temps d'exécution de 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
celui-ci nous croit juste aveuglément: nous avons dit qu'il y a une D
adresse à b2s[1]
, et le compilateur ne fait aucun calcul de décalage.
Mais c'est faux, car D est en fait à 0x7fffffffc930, ce qui est à 0x7fffffffc940 est la structure de type B2 à l'intérieur de D! La corbeille est donc accessible.
Nous pouvons le confirmer de l' -O0
assemblée horrible qui déplace simplement la valeur:
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)
Questions connexes:
Testé sur Ubuntu 18.04 amd64, GCC 7.4.0.