Quais são os usos adequados de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Elenco no estilo C
(type)value
- Elenco no estilo de função
type(value)
Como alguém decide qual usar em quais casos específicos?
Quais são os usos adequados de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Como alguém decide qual usar em quais casos específicos?
Respostas:
static_cast
é o primeiro elenco que você deve tentar usar. Ele faz coisas como conversões implícitas entre tipos (como int
para float
ou ponteiro para void*
) e também pode chamar funções de conversão explícitas (ou implícitas). Em muitos casos, declarar explicitamente static_cast
não é necessário, mas é importante observar que a T(something)
sintaxe é equivalente (T)something
e deve ser evitada (mais sobre isso mais tarde). A T(something, something_else)
é seguro, no entanto, e garantido para chamar o construtor.
static_cast
também pode transmitir através de hierarquias de herança. É desnecessário ao converter para cima (em direção a uma classe base), mas ao converter para baixo, ele pode ser usado desde que não seja transmitido por virtual
herança. Entretanto, ele não verifica, e é um comportamento indefinido static_cast
diminuir uma hierarquia para um tipo que não é realmente o tipo do objeto.
const_cast
pode ser usado para remover ou adicionar const
a uma variável; nenhum outro elenco C ++ é capaz de removê-lo (nem mesmo reinterpret_cast
). É importante observar que a modificação de um const
valor anterior é indefinida apenas se a variável original for const
; se você usá-lo para retirar const
uma referência a algo que não foi declarado const
, é seguro. Isso pode ser útil ao sobrecarregar funções-membro com base em const
, por exemplo. Também pode ser usado para adicionar const
a um objeto, como chamar uma sobrecarga de função de membro.
const_cast
também funciona de maneira semelhante volatile
, embora isso seja menos comum.
dynamic_cast
é usado exclusivamente para lidar com polimorfismo. Você pode converter um ponteiro ou referência a qualquer tipo polimórfico para qualquer outro tipo de classe (um tipo polimórfico possui pelo menos uma função virtual, declarada ou herdada). Você pode usá-lo para mais do que apenas lançar para baixo - você pode lançar para o lado ou até outra cadeia. O dynamic_cast
procurará o objeto desejado e o retornará, se possível. Se não puder, retornará nullptr
no caso de um ponteiro ou lançará std::bad_cast
no caso de uma referência.
dynamic_cast
tem algumas limitações, no entanto. Não funciona se houver vários objetos do mesmo tipo na hierarquia de herança (o chamado 'diamante temido') e você não estiver usando virtual
herança. Ele também só pode passar por herança pública - sempre falhará em atravessar protected
ou private
herança. Isso raramente é um problema, no entanto, como essas formas de herança são raras.
reinterpret_cast
é o elenco mais perigoso e deve ser usado com moderação. Ele transforma um tipo diretamente em outro - como converter o valor de um ponteiro para outro, ou armazenar um ponteiro em um int
ou em todos os tipos de outras coisas desagradáveis. Em geral, a única garantia que você obtém reinterpret_cast
é que, normalmente, se você converter o resultado de volta ao tipo original, obterá exatamente o mesmo valor (mas não se o tipo intermediário for menor que o tipo original). Há várias conversões que reinterpret_cast
também não podem ser realizadas. É usado principalmente para conversões e manipulações de bits particularmente estranhas, como transformar um fluxo de dados brutos em dados reais ou armazenar dados nos bits mais baixos de um ponteiro para dados alinhados.
Molde-estilo C e fundido de estilo função são moldes usando (type)object
ou type(object)
, respectivamente, e são funcionalmente equivalentes. Eles são definidos como o primeiro dos seguintes itens bem-sucedidos:
const_cast
static_cast
(embora ignorando as restrições de acesso)static_cast
(veja acima), então const_cast
reinterpret_cast
reinterpret_cast
, então const_cast
Portanto, ele pode ser usado como um substituto para outros elencos em alguns casos, mas pode ser extremamente perigoso devido à capacidade de se transformar em um reinterpret_cast
, e o último deve ser preferido quando a conversão explícita for necessária, a menos que você tenha certeza de que static_cast
terá êxito ou reinterpret_cast
falhará . Mesmo assim, considere a opção mais longa e explícita.
As transmissões no estilo C também ignoram o controle de acesso ao executar a static_cast
, o que significa que elas têm a capacidade de executar uma operação que nenhuma outra transmissão pode. No entanto, isso é principalmente uma confusão e, na minha opinião, é apenas mais um motivo para evitar elencos no estilo C.
const
(nem mesmo reinterpret_cast
)" ... sério? Que tal reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
muitas vezes é a arma de escolha quando se trata de jogo de uma API de tipos de dados opacos
Use dynamic_cast
para converter ponteiros / referências dentro de uma hierarquia de herança.
Use static_cast
para conversões de tipo comuns.
Use reinterpret_cast
para reinterpretação de baixo nível de padrões de bits. Use com extrema cautela.
Use const_cast
para jogar fora const/volatile
. Evite isso, a menos que você esteja preso usando uma API const-incorreta.
(Muita explicação teórica e conceitual foi dada acima)
Abaixo estão alguns exemplos práticos quando usei static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Também se refere a isso para entender a explicação: 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
só funciona entre tipos com conversões definidas, relação visível por herança ou para / de void *
. Para todo o resto, existem outros elencos. É permitido reinterpret cast
a qualquer char *
tipo permitir a leitura da representação de qualquer objeto - e um dos únicos casos em que essa palavra-chave é útil, e não um gerador desenfreado de implementação / comportamento indefinido. Mas isso não é considerado uma conversão 'normal', portanto não é permitido pelo (geralmente) muito conservador static_cast
.
Pode ajudar se você conhecer um pouco de internos ...
static_cast
static_cast
para eles.A
para B
, o construtor de static_cast
chamadas B
passa A
como param. Como alternativa, A
poderia ter um operador de conversão (ou seja A::operator B()
). Se B
não tiver esse construtor ou A
não tiver um operador de conversão, você receberá um erro de tempo de compilação.A*
para B*
sempre é bem-sucedida se A e B estão na hierarquia de herança (ou nula), caso contrário, você obtém um erro de compilação.A&
a B&
.dynamic_cast
(Base*)
a (Derived*)
pode falhar se apontador não é, na verdade, do tipo derivado.A*
a B*
, se elenco é dynamic_cast então inválida retornará nullptr.A&
a B&
se fundido é inválido, em seguida, dynamic_cast vai jogar exceção bad_cast.const_cast
set<T>
que apenas retorna seus elementos como const para garantir que você não altere sua chave. No entanto, se sua intenção é modificar os membros não-chave do objeto, tudo ficará bem. Você pode usar const_cast para remover constness.T& SomeClass::foo()
também const T& SomeClass::foo() const
. Para evitar duplicação de código, você pode aplicar const_cast para retornar o valor de uma função de outra.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.
Você recebe UB, o que pode resultar em um segfault em tempo de execução, se você tiver sorte. 2. Modelos dinâmicos também podem ser usados em cross casting. 3. O elenco Const pode resultar em UB em alguns casos. Usar mutable
pode ser uma opção melhor para implementar a constância lógica.
mutable
, fundição cruz etc.
Será que isso responde sua pergunta?
Eu nunca usei reinterpret_cast
e me pergunto se encontrar um caso que precise dele não é um cheiro de design ruim. Na base de código em que trabalho dynamic_cast
é muito usada. A diferença static_cast
é que a dynamic_cast
verificação em tempo de execução pode ser (mais segura) ou não (mais sobrecarga) o que você deseja (consulte msdn ).
reinterpret_cast
para extrair pedaços de dados de uma matriz. Por exemplo, se eu tenho um char*
contendo um grande buffer cheio de dados binários compactados que eu preciso percorrer e obter primitivas individuais de tipos variados. Algo parecido com isto: #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
, não há muitos usos para isso.
reinterpret_cast
usado por uma razão. Vi dados brutos do objeto armazenados em um tipo de dados "blob" em um banco de dados e, quando os dados são recuperados do banco de dados, reinterpret_cast
são usados para transformar esses dados brutos no objeto.
Além das outras respostas até agora, aqui está um exemplo não óbvio, onde static_cast
não é suficiente para que reinterpret_cast
seja necessário. Suponha que exista uma função que em um parâmetro de saída retorne ponteiros para objetos de diferentes classes (que não compartilham uma classe base comum). Um exemplo real dessa função é CoCreateInstance()
(consulte o último parâmetro, que é de fato void**
). Suponha que você solicite determinada classe de objeto dessa função, para que você conheça com antecedência o tipo do ponteiro (o que costuma fazer para objetos COM). Nesse caso, você não pode converter o ponteiro para o ponteiro void**
com static_cast
: você precisa reinterpret_cast<void**>(&yourPointer)
.
Em código:
#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) );
No entanto, static_cast
funciona para ponteiros simples (não ponteiros para ponteiros), portanto, o código acima pode ser reescrito para evitar reinterpret_cast
(ao preço de uma variável extra) da seguinte maneira:
#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)
vez de static_cast<void**>(&pNetFwPolicy2)
?
Enquanto outras respostas descrevem bem todas as diferenças entre os lançamentos em C ++, gostaria de acrescentar uma breve nota sobre por que você não deve usar os lançamentos em estilo C (Type) var
e Type(var)
.
Para iniciantes em C ++, as transmissões no estilo C parecem ser a operação de superconjunto nas transmissões em C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) e alguém poderia preferir elas nas transmissões em C ++ . De fato, o elenco no estilo C é o superconjunto e mais curto para escrever.
O principal problema dos elencos no estilo C é que eles ocultam a real intenção do desenvolvedor. As conversões no estilo C podem executar praticamente todos os tipos de conversão, desde conversões normalmente seguras feitas por static_cast <> () e dynamic_cast <> () até conversões potencialmente perigosas como const_cast <> (), onde o modificador const pode ser removido para que as variáveis const pode ser modificado e reinterpret_cast <> () que pode até reinterpretar valores inteiros em ponteiros.
Aqui está a amostra.
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.
A principal razão pela qual os lançamentos de C ++ foram adicionados à linguagem foi permitir que um desenvolvedor esclarecesse suas intenções - por que ele fará esse elenco. Ao usar conversões no estilo C, perfeitamente válidas em C ++, você torna seu código menos legível e mais suscetível a erros, especialmente para outros desenvolvedores que não criaram seu código. Portanto, para tornar seu código mais legível e explícito, você sempre deve preferir conversões em C ++ em vez de conversões em estilo C.
Aqui está uma pequena citação do livro de Bjarne Stroustrup (o autor do C ++) The C ++ Programming Language 4th edition - page 302.
Essa conversão no estilo C é muito mais perigosa do que os operadores de conversão nomeados, porque a notação é mais difícil de detectar em um programa grande e o tipo de conversão pretendida pelo programador não é explícito.
Para entender, vamos considerar abaixo o trecho de código:
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;
}
Somente a linha (4) é compilada sem erros. Somente reinterpret_cast pode ser usado para converter um ponteiro em um objeto em um ponteiro em qualquer tipo de objeto não relacionado.
Um exemplo a ser observado é: O dynamic_cast falharia em tempo de execução; no entanto, na maioria dos compiladores, também falha na compilação porque não há funções virtuais na estrutura do ponteiro sendo convertido, o que significa que o dynamic_cast funcionará apenas com ponteiros de classe polimórficos .
Quando usar o elenco do C ++ :
static_cast
vs dynamic_cast
vs reinterpret_cast
interna vista em um downcast / upcast
Nesta resposta, quero comparar esses três mecanismos em um exemplo concreto de upcast / downcast e analisar o que acontece com os ponteiros / memória / assembly subjacentes para fornecer uma compreensão concreta de como eles se comparam.
Acredito que isso dará uma boa intuição sobre como esses elencos são diferentes:
static_cast
: faz um deslocamento de endereço no tempo de execução (baixo impacto no tempo de execução) e nenhuma verificação de segurança de que um downcast está correto.
dyanamic_cast
: faz o mesmo endereço compensado no tempo de execução static_cast
, mas também e uma verificação de segurança dispendiosa de que um downcast está correto usando RTTI.
Essa verificação de segurança permite consultar se um ponteiro de classe base é de um determinado tipo em tempo de execução, verificando um retorno nullptr
que indica um downcast inválido.
Portanto, se o seu código não conseguir verificar isso nullptr
e executar uma ação válida sem interrupção, você deve apenas usar em static_cast
vez da conversão dinâmica.
Se uma interrupção é a única ação que seu código pode executar, talvez você queira apenas ativar as dynamic_cast
compilações in debug (-NDEBUG
) e usar o static_cast
contrário, por exemplo, como feito aqui , para não desacelerar suas execuções rápidas.
reinterpret_cast
: não faz nada em tempo de execução, nem mesmo o deslocamento do endereço. O ponteiro deve apontar exatamente para o tipo correto, nem mesmo uma classe base funciona. Você geralmente não deseja isso, a menos que fluxos de bytes brutos estejam envolvidos.
Considere o seguinte exemplo 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;
}
Compile, execute e desmonte com:
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
onde setarch
esta usado para desativar o ASLR para facilitar a comparação de execuções.
Saída possível:
&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
Agora, como mencionado em: https://en.wikipedia.org/wiki/Virtual_method_table , a fim de oferecer suporte às chamadas de método virtual com eficiência, a estrutura de dados da memória D
deve se parecer com:
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
O fato principal é que a estrutura de dados da memória D
contém dentro dela uma estrutura de memória compatível com a de B1
e com a deB2
internamente.
Portanto, chegamos à conclusão crítica:
um upcast ou downcast precisa apenas mudar o valor do ponteiro por um valor conhecido em tempo de compilação
Dessa maneira, quando D
é passado para a matriz de tipos base, o tipo de conversão calcula esse deslocamento e aponta algo que se parece exatamente com um válido B2
na memória:
b2s[1] = &d;
exceto que este possui a vtable em D
vez de B2
e, portanto, todas as chamadas virtuais funcionam de forma transparente.
Agora, finalmente podemos voltar ao tipo de vazamento e à análise de nosso exemplo concreto.
A partir da saída stdout, vemos:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Portanto, o implícito static_cast
feito lá calculou corretamente o deslocamento da D
estrutura de dados completa em 0x7fffffffc930 para a B2
similar que está em 0x7fffffffffc940. Também inferimos que o que está entre 0x7fffffffc930 e 0x7fffffffcc40 é provavelmente os B1
dados e a tabela.
Então, nas seções de baixa, agora é fácil entender como os inválidos falham e por que:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: o compilador subiu 0x10 nos bytes de tempo de compilação para tentar ir de um B2
para o que continhaD
Mas porque b2s[0]
não era umD
, agora aponta para uma região de memória indefinida.
A desmontagem é:
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)
então vemos que o GCC faz:
D
que não existedynamic_cast<D*>(b2s[0]) 0
: C ++ realmente descobriu que o elenco era inválido e retornado nullptr
!
Não há como isso ser feito em tempo de compilação, e confirmaremos isso a partir da desmontagem:
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)
Primeiro, há uma verificação NULL e ela retorna NULL se a entrada for NULL.
Caso contrário, ele configura alguns argumentos no RDX, RSI e RDI e chama __dynamic_cast
.
Não tenho paciência para analisar isso mais agora, mas, como outros disseram, a única maneira de isso funcionar é __dynamic_cast
acessar algumas estruturas de dados RTTI na memória extras que representam a hierarquia de classes.
Portanto, ele deve começar a partir da B2
entrada para essa tabela e, em seguida, percorrer essa hierarquia de classes até encontrar a tabela de tabela para uma D
conversão de tipob2s[0]
.
É por isso que reinterpretar o elenco é potencialmente caro! Aqui está um exemplo em que um patch de um liner que converte a dynamic_cast
em um static_cast
em um projeto complexo reduziu o tempo de execução em 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
este acredita em nós cegamente: dissemos que existe um D
endereço at b2s[1]
, e o compilador não faz cálculos de deslocamento.
Mas isso está errado, porque D está realmente em 0x7fffffffc930, o que está em 0x7fffffffcc40 é a estrutura do tipo B2 dentro de D! Portanto, o lixo é acessado.
Podemos confirmar isso na -O0
montagem horrenda que apenas move o 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)
Perguntas relacionadas:
Testado no Ubuntu 18.04 amd64, GCC 7.4.0.