¿Cuáles son los usos adecuados de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Reparto estilo C
(type)value
- Elenco de estilo funcional
type(value)
¿Cómo se decide cuál usar en qué casos específicos?
¿Cuáles son los usos adecuados de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
¿Cómo se decide cuál usar en qué casos específicos?
Respuestas:
static_cast
es el primer lanzamiento que debes intentar usar. Hace cosas como conversiones implícitas entre tipos (como int
to float
o pointer to void*
), y también puede llamar a funciones de conversión explícitas (o implícitas). En muchos casos, static_cast
no es necesario declarar explícitamente , pero es importante tener en cuenta que la T(something)
sintaxis es equivalente (T)something
y debe evitarse (más sobre eso más adelante). T(something, something_else)
Sin embargo, A es seguro y está garantizado para llamar al constructor.
static_cast
También puede emitir a través de jerarquías de herencia. No es necesario cuando se lanza hacia arriba (hacia una clase base), pero cuando se lanza hacia abajo se puede usar siempre que no se lance por virtual
herencia. Sin embargo, no realiza comprobaciones y es un comportamiento indefinido static_cast
reducir una jerarquía a un tipo que en realidad no es el tipo del objeto.
const_cast
se puede usar para eliminar o agregar const
a una variable; ningún otro elenco de C ++ es capaz de eliminarlo (ni siquiera reinterpret_cast
). Es importante tener en cuenta que modificar un const
valor anterior solo no está definido si la variable original es const
; si lo usa para quitar const
una referencia a algo con lo que no se declaró const
, es seguro. Esto puede ser útil al sobrecargar funciones de miembros basadas const
, por ejemplo. También se puede usar para agregar const
a un objeto, como llamar a una sobrecarga de la función miembro.
const_cast
También funciona de manera similar volatile
, aunque eso es menos común.
dynamic_cast
Se utiliza exclusivamente para el manejo del polimorfismo. Puede lanzar un puntero o referencia a cualquier tipo polimórfico a cualquier otro tipo de clase (un tipo polimórfico tiene al menos una función virtual, declarada o heredada). Puede usarlo para algo más que lanzar hacia abajo: puede lanzar hacia los lados o incluso hacia arriba en otra cadena. El dynamic_cast
buscará el objeto deseado y devolverlo si es posible. Si no puede, regresará nullptr
en el caso de un puntero, o arrojará std::bad_cast
en el caso de una referencia.
dynamic_cast
Sin embargo, tiene algunas limitaciones. No funciona si hay varios objetos del mismo tipo en la jerarquía de herencia (el llamado 'diamante temido') y no está utilizando la virtual
herencia. También solo puede pasar por herencia pública: siempre fallará en viajar protected
o en private
herencia. Sin embargo, esto rara vez es un problema, ya que tales formas de herencia son raras.
reinterpret_cast
es el yeso más peligroso y debe usarse con moderación. Convierte un tipo directamente en otro, como convertir el valor de un puntero a otro, o almacenar un puntero en una int
o toda otra clase de cosas desagradables. En gran medida, la única garantía con la que obtiene reinterpret_cast
es que normalmente si devuelve el resultado al tipo original, obtendrá exactamente el mismo valor (pero no si el tipo intermedio es más pequeño que el tipo original). También hay varias conversiones que reinterpret_cast
no pueden hacer. Se utiliza principalmente para conversiones particularmente extrañas y manipulaciones de bits, como convertir un flujo de datos sin procesar en datos reales o almacenar datos en los bits bajos de un puntero en datos alineados.
La conversión de estilo C y la conversión de estilo funcional son conversiones utilizando (type)object
o type(object)
, respectivamente, y son funcionalmente equivalentes. Se definen como el primero de los siguientes que tiene éxito:
const_cast
static_cast
(aunque ignorando las restricciones de acceso)static_cast
(ver arriba), entonces const_cast
reinterpret_cast
reinterpret_cast
, entonces const_cast
Por lo tanto, puede usarse como reemplazo de otros lanzamientos en algunos casos, pero puede ser extremadamente peligroso debido a la capacidad de convertirse en a reinterpret_cast
, y este último debe preferirse cuando se necesita un lanzamiento explícito, a menos que esté seguro de que static_cast
tendrá éxito o reinterpret_cast
fallará . Incluso entonces, considere la opción más larga y más explícita.
Los lanzamientos de estilo C también ignoran el control de acceso al realizar un static_cast
, lo que significa que tienen la capacidad de realizar una operación que ningún otro lanzamiento puede. Sin embargo, esto es principalmente un error, y en mi opinión es solo otra razón para evitar los moldes de estilo C.
const
(ni siquiera reinterpret_cast
)" ... ¿en serio? ¿Qué hay de reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
menudo es el arma de elección cuando se trata con un conjunto de tipos de datos opacos de una API
Se usa dynamic_cast
para convertir punteros / referencias dentro de una jerarquía de herencia.
Úselo static_cast
para conversiones de tipo ordinario.
Úselo reinterpret_cast
para la reinterpretación de bajo nivel de patrones de bits. Usar con extrema precaución.
Úselo const_cast
para desechar const/volatile
. Evita esto a menos que estés atascado usando una API const-incorrecta
(Se ha dado mucha explicación teórica y conceptual arriba)
A continuación se presentan algunos ejemplos prácticos cuando utilicé static_cast , dynamic_cast , const_cast , reinterpret_cast .
(También hace referencia a esto para comprender la explicación: 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
solo funciona entre tipos con conversiones definidas, relación visible por herencia o hacia / desde void *
. Para todo lo demás, hay otros moldes. reinterpret cast
a cualquier char *
tipo se le permite leer la representación de cualquier objeto, y uno de los únicos casos en que esa palabra clave es útil, no un generador desenfrenado de implementación / comportamiento indefinido. Pero esto no se considera una conversión 'normal', por lo que no está permitido por los (generalmente) muy conservadores static_cast
.
Podría ayudar si conoces un poco de aspectos internos ...
static_cast
static_cast
para ellos.A
a B
, el constructor de static_cast
llamadas B
pasa A
como parámetro. Alternativamente, A
podría tener un operador de conversión (es decir A::operator B()
). Si B
no tiene dicho constructor, o A
no tiene un operador de conversión, entonces obtiene un error de tiempo de compilación.A*
a B*
siempre tiene éxito si A y B están en la jerarquía de herencia (o nula), de lo contrario, obtendrá un error de compilación.A&
a B&
.Dynamic_cast
(Base*)
a (Derived*)
puede fallar si el puntero no es realmente de tipo derivado.A*
que B*
, si es fundido moldeado dinámico no válido a continuación volverá nullptr.A&
a B&
si la conversión no es válida, dynamic_cast generará una excepción bad_cast.const_cast
set<T>
que solo devuelve sus elementos como constante para asegurarse de que no cambie su clave. Sin embargo, si su intención es modificar los miembros no clave del objeto, entonces debería estar bien. Puede usar const_cast para eliminar la constness.T& SomeClass::foo()
tan bien como const T& SomeClass::foo() const
. Para evitar la duplicación de código, puede aplicar const_cast para devolver el valor de una función de otra.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.
Obtiene UB que puede resultar en una falla predeterminada en el tiempo de ejecución si tiene suerte. 2. Los lanzamientos dinámicos también se pueden usar en el lanzamiento cruzado. 3. Los moldes de Const pueden dar lugar a UB en algunos casos. Usar mutable
puede ser una mejor opción para implementar la constidad lógica.
mutable
¿ Responde esto a tu pregunta?
Nunca lo he usado reinterpret_cast
, y me pregunto si encontrarse con un caso que lo necesita no es un olor a mal diseño. En la base de código en la que trabajo dynamic_cast
se usa mucho. La diferencia con static_cast
es que una dynamic_cast
comprobación de tiempo de ejecución puede ser (más segura) o no (más sobrecarga) lo que desea (consulte msdn ).
reinterpret_cast
para extraer datos de una matriz. Por ejemplo, si tengo un char*
búfer grande lleno de datos binarios empaquetados que necesito mover y obtener primitivas individuales de diferentes tipos. Algo así: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
, no hay muchos usos para ello.
reinterpret_cast
usado por una razón. He visto datos de objetos en bruto almacenados en un tipo de datos "blob" en una base de datos, luego, cuando los datos se recuperan de la base de datos, reinterpret_cast
se utilizan para convertir estos datos en bruto en el objeto.
Además de las otras respuestas hasta ahora, aquí hay un ejemplo obvio en el static_cast
que no es suficiente para que reinterpret_cast
sea necesario. Supongamos que hay una función que en un parámetro de salida devuelve punteros a objetos de diferentes clases (que no comparten una clase base común). Un ejemplo real de dicha función es CoCreateInstance()
(ver el último parámetro, que de hecho es void**
). Suponga que solicita una clase particular de objeto de esta función, de modo que sepa de antemano el tipo del puntero (lo que suele hacer con los objetos COM). En este caso, no puede convertir el puntero en su puntero void**
con static_cast
: necesita reinterpret_cast<void**>(&yourPointer)
.
En codigo:
#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) );
Sin embargo, static_cast
funciona para punteros simples (no punteros a punteros), por lo que el código anterior se puede reescribir para evitar reinterpret_cast
(al precio de una variable adicional) de la siguiente manera:
#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)
lugar de static_cast<void**>(&pNetFwPolicy2)
?
Si bien otras respuestas describieron muy bien todas las diferencias entre los moldes de C ++, me gustaría agregar una breve nota de por qué no debería usar moldes de estilo C (Type) var
y Type(var)
.
Para los principiantes de C ++, los lanzamientos de estilo C parecen ser la operación de superconjunto sobre los lanzamientos de C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) y alguien podría preferirlos sobre los lanzamientos de C ++ . De hecho, el elenco de estilo C es el superconjunto y más corto de escribir.
El principal problema de los elencos estilo C es que ocultan la verdadera intención del desarrollador del elenco. Los lanzamientos de estilo C pueden realizar prácticamente todos los tipos de lanzamientos desde lanzamientos normalmente seguros realizados por static_cast <> () y dynamic_cast <> () a lanzamientos potencialmente peligrosos como const_cast <> (), donde el modificador const puede eliminarse para que las variables const se puede modificar y reinterpretar_cast <> () que incluso puede reinterpretar valores enteros en punteros.
Aquí está la muestra.
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 razón principal por la que se agregaron los lanzamientos de C ++ al lenguaje fue para permitir que un desarrollador aclare sus intenciones, por qué va a hacer ese lanzamiento. Al usar modelos de estilo C que son perfectamente válidos en C ++, está haciendo que su código sea menos legible y más propenso a errores, especialmente para otros desarrolladores que no crearon su código. Por lo tanto, para que su código sea más legible y explícito, siempre debe preferir los moldes de C ++ sobre los moldes de estilo C.
Aquí hay una breve cita del libro de Bjarne Stroustrup (autor de C ++) The C ++ Programming Language 4th edition - página 302.
Este reparto de estilo C es mucho más peligroso que los operadores de conversión nombrados porque la notación es más difícil de detectar en un programa grande y el tipo de conversión previsto por el programador no es explícito.
Para comprender, consideremos el fragmento de código a continuación:
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;
}
Solo la línea (4) compila sin error. Solo reinterpret_cast puede usarse para convertir un puntero a un objeto a un puntero a cualquier tipo de objeto no relacionado.
Algo que debe tenerse en cuenta es que: Dynamic_cast fallará en tiempo de ejecución, sin embargo, en la mayoría de los compiladores también fallará al compilar porque no hay funciones virtuales en la estructura del puntero que se está lanzando , lo que significa que dynamic_cast funcionará solo con punteros de clase polimórficos .
Cuándo usar C ++ cast :
static_cast
vs dynamic_cast
vs reinterpret_cast
internos ver en un downcast / upcast
En esta respuesta, quiero comparar estos tres mecanismos en un ejemplo concreto de upcast / downcast y analizar qué sucede con los punteros subyacentes / memoria / ensamblaje para dar una comprensión concreta de cómo se comparan.
Creo que esto dará una buena intuición sobre cómo esos moldes son diferentes:
static_cast
: hace un desplazamiento de dirección en tiempo de ejecución (bajo impacto de tiempo de ejecución) y no verifica la seguridad de que una descarga sea correcta.
dyanamic_cast
: hace el mismo desplazamiento de dirección en tiempo de ejecución static_cast
, pero también y una costosa verificación de seguridad de que un downcast es correcto usando RTTI.
Esta comprobación de seguridad le permite consultar si un puntero de clase base es de un tipo dado en tiempo de ejecución al verificar una devolución de la nullptr
cual indica un downcast no válido.
Por lo tanto, si su código no puede verificar eso nullptr
y tomar una acción válida de no aborto, solo debe usarlo en static_cast
lugar de la conversión dinámica.
Si un aborto es la única acción que puede tomar su código, tal vez solo desee habilitar las dynamic_cast
compilaciones en depuración ( -NDEBUG
), y usar lo static_cast
contrario, por ejemplo, como se hace aquí , para no ralentizar sus ejecuciones rápidas.
reinterpret_cast
: no hace nada en tiempo de ejecución, ni siquiera el desplazamiento de la dirección. El puntero debe apuntar exactamente al tipo correcto, ni siquiera una clase base funciona. Por lo general, no desea esto a menos que estén involucradas secuencias de bytes sin procesar.
Considere el siguiente ejemplo de código:
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;
}
Compila, ejecuta y desmonta con:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
donde setarch
se usa para deshabilitar ASLR para que sea más fácil comparar ejecuciones.
Salida posible:
&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
Ahora, como se menciona en: https://en.wikipedia.org/wiki/Virtual_method_table para admitir las llamadas de métodos virtuales de manera eficiente, la estructura de datos de la memoria D
tiene que ser algo así como:
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
El hecho clave es que la estructura de datos de memoria D
contiene dentro de ella una estructura de memoria compatible con la de B1
y la de B2
internamente.
Por lo tanto, llegamos a la conclusión crítica:
una subida o bajada solo necesita cambiar el valor del puntero por un valor conocido en tiempo de compilación
De esta manera, cuando D
se pasa a la matriz de tipos base, el tipo de conversión calcula ese desplazamiento y señala algo que se ve exactamente como válido B2
en la memoria:
b2s[1] = &d;
excepto que este tiene la vtable para en D
lugar de B2
, y por lo tanto todas las llamadas virtuales funcionan de manera transparente.
Ahora, finalmente podemos volver al tipo de fundición y al análisis de nuestro ejemplo concreto.
De la salida stdout vemos:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Por lo tanto, lo implícito static_cast
hecho allí calculó correctamente el desplazamiento de la D
estructura de datos completa en 0x7fffffffc930 a la B2
similar que está en 0x7fffffffc940. También inferimos que lo que se encuentra entre 0x7fffffffc930 y 0x7fffffffc940 es probable que sean los B1
datos y vtable.
Luego, en las secciones abatidas, ahora es fácil entender cómo fallan los no válidos y por qué:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: el compilador subió 0x10 en tiempo de compilación de bytes para intentar ir de un B2
aD
Pero debido a que b2s[0]
no era un D
, ahora apunta a una región de memoria indefinida.
El desmontaje es:
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)
entonces vemos que GCC hace:
D
que no existedynamic_cast<D*>(b2s[0]) 0
: ¡C ++ descubrió que el reparto no era válido y se devolvió nullptr
!
No hay forma de que esto se pueda hacer en tiempo de compilación, y lo confirmaremos desde el desmontaje:
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)
Primero hay una comprobación NULL, y devuelve NULL si la entrada es NULL.
De lo contrario, establece algunos argumentos en RDX, RSI y RDI y las llamadas __dynamic_cast
.
No tengo la paciencia para analizar esto más a fondo ahora, pero como otros dijeron, la única forma de que esto funcione es __dynamic_cast
acceder a algunas estructuras de datos en memoria RTTI adicionales que representan la jerarquía de clases.
Por lo tanto, debe comenzar desde la B2
entrada para esa tabla, luego recorrer esta jerarquía de clases hasta que encuentre que la tabla vtable para un D
typecast b2s[0]
.
¡Es por eso que reinterpretar el elenco es potencialmente costoso! ¡Aquí hay un ejemplo en el que un parche de una línea que convierte dynamic_cast
a a static_cast
en un proyecto complejo reduce el tiempo de ejecución en un 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
este simplemente nos cree ciegamente: dijimos que hay una D
dirección at b2s[1]
, y el compilador no realiza cálculos de compensación.
Pero esto está mal, porque D está realmente en 0x7fffffffc930, ¡lo que está en 0x7fffffffc940 es la estructura similar a B2 dentro de D! Entonces se accede a la basura.
Podemos confirmar esto desde el horrendo -O0
ensamblaje que solo mueve el valor:
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)
Preguntas relacionadas:
Probado en Ubuntu 18.04 amd64, GCC 7.4.0.