Quando static_cast, dynamic_cast, const_cast e reinterpret_cast devem ser usados?

2496

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?

e.James
fonte
3
Para alguns exemplos concretos úteis do uso de diferentes tipos de elencos, você pode verificar a primeira resposta em uma pergunta semelhante neste outro tópico .
22417 TeaMonkie
2
Você pode encontrar respostas realmente boas para sua pergunta acima. Mas eu gostaria de colocar mais um ponto aqui, @ e.James "Não há nada que esses novos operadores de conversão de c ++ possam fazer e que a conversão de estilo c não possa. Eles são adicionados mais ou menos para melhorar a legibilidade do código."
BreakBadSP
@BreakBadSP Os novos lançamentos não são apenas para uma melhor legibilidade do código. Eles estão lá para tornar mais difícil fazer coisas perigosas, como rejeitar const ou lançar ponteiros em vez de seus valores. static_cast tem muito menos possibilidades de fazer algo perigoso do que um elenco de estilo ac!
FourtyTwo
@FourtyTwo concordou
BreakBadSP

Respostas:

2571

static_casté o primeiro elenco que você deve tentar usar. Ele faz coisas como conversões implícitas entre tipos (como intpara floatou ponteiro para void*) e também pode chamar funções de conversão explícitas (ou implícitas). Em muitos casos, declarar explicitamente static_castnão é necessário, mas é importante observar que a T(something)sintaxe é equivalente (T)somethinge deve ser evitada (mais sobre isso mais tarde). A T(something, something_else)é seguro, no entanto, e garantido para chamar o construtor.

static_casttambé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 virtualherança. Entretanto, ele não verifica, e é um comportamento indefinido static_castdiminuir uma hierarquia para um tipo que não é realmente o tipo do objeto.


const_castpode ser usado para remover ou adicionar consta uma variável; nenhum outro elenco C ++ é capaz de removê-lo (nem mesmo reinterpret_cast). É importante observar que a modificação de um constvalor anterior é indefinida apenas se a variável original for const; se você usá-lo para retirar constuma 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 consta um objeto, como chamar uma sobrecarga de função de membro.

const_casttambé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_castprocurará o objeto desejado e o retornará, se possível. Se não puder, retornará nullptrno caso de um ponteiro ou lançará std::bad_castno caso de uma referência.

dynamic_casttem 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 virtualherança. Ele também só pode passar por herança pública - sempre falhará em atravessar protectedou privateheranç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 intou 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_casttambé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)objectou 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_castterá êxito ou reinterpret_castfalhará . 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.

coppro
fonte
17
dynamic_cast é apenas para tipos polimórficos. você só precisa usá-lo quando estiver transmitindo para uma classe derivada. static_cast é certamente a primeira opção, a menos que você precise especificamente da funcionalidade de dynamic_cast. Não é um elenco miraculoso de bala de prata em geral.
jalf
2
Ótima resposta! Uma observação rápida: static_cast pode ser necessário para converter a hierarquia no caso de você ter um Derivado * & para converter em Base * &, pois ponteiros / referências duplos não lançam automaticamente a hierarquia. Me deparei com essa situação (francamente, não comum) dois minutos atrás. ;-)
bartgol
5
* "nenhum outro elenco de C ++ é capaz de remover const(nem mesmo reinterpret_cast)" ... sério? Que tal reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
precisa saber é o seguinte
29
Eu acho que um detalhe importante que faltava acima é que o dynamic_cast tem uma penalidade de desempenho em tempo de execução em comparação com o static ou reinterpret_cast. Isso é importante, por exemplo, em software em tempo real.
Jritz42
5
Pode valer a pena mencionar que reinterpret_castmuitas vezes é a arma de escolha quando se trata de jogo de uma API de tipos de dados opacos
Classe Skeleton
333

Use dynamic_castpara converter ponteiros / referências dentro de uma hierarquia de herança.

Use static_castpara conversões de tipo comuns.

Use reinterpret_castpara reinterpretação de baixo nível de padrões de bits. Use com extrema cautela.

Use const_castpara jogar fora const/volatile. Evite isso, a menos que você esteja preso usando uma API const-incorreta.

Fred Larson
fonte
2
Cuidado com dynamic_cast. Ele se baseia no RTTI e isso não funcionará conforme o esperado nos limites das bibliotecas compartilhadas. Simplesmente porque você cria uma biblioteca executável e compartilhada de forma independente, não há uma maneira padronizada de sincronizar o RTTI entre diferentes compilações. Por esse motivo, na biblioteca Qt, existe qobject_cast <> que usa as informações do tipo QObject para verificar os tipos.
user3150128
198

(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);
}
Sumit Arora
fonte
31
A teoria de algumas das outras respostas é boa, mas ainda confusa, ver esses exemplos depois de ler as outras respostas faz com que todas façam sentido. Isso é sem os exemplos, eu ainda não tinha certeza, mas com eles, agora tenho certeza sobre o que as outras respostas significam.
Solx
1
Sobre o último uso de reinterpret_cast: não é o mesmo que usar static_cast<char*>(&val)?
Lorenzo Belli 27/05
3
@LorenzoBelli Claro que não. Você tentou? O último não é C ++ válido e bloqueia a compilação. static_castsó 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 casta 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.
underscore_d
2
reinterpret_cast é bastante comum quando você está trabalhando com software do sistema, como bancos de dados. Na maioria dos casos, você escreve seu próprio gerenciador de páginas, que não tem idéia sobre qual é o tipo de dados armazenado na página e retorna apenas um ponteiro nulo. Cabe aos níveis mais altos fazer uma reinterpretação do elenco e inferir como quiser.
Sohaib 17/05
1
O exemplo const_cast exibe comportamento indefinido. Uma variável declarada como const não pode ser desconstituída. No entanto, uma variável declarada como não-const que é passada para uma função que faz referência a const pode ser deformada nessa função sem que seja UB.
9788 Johann Gerell
99

Pode ajudar se você conhecer um pouco de internos ...

static_cast

  • O compilador C ++ já sabe como converter entre tipos de scaler, como float para int. Use static_castpara eles.
  • Quando você pede ao compilador para converter do tipo Apara B, o construtor de static_castchamadas Bpassa Acomo param. Como alternativa, Apoderia ter um operador de conversão (ou seja A::operator B()). Se Bnão tiver esse construtor ou Anão tiver um operador de conversão, você receberá um erro de tempo de compilação.
  • A conversão de 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.
  • Entendi : Se você converter o ponteiro base para o ponteiro derivado, mas se o objeto real não for realmente um tipo derivado, não haverá erro. Você fica com um ponteiro ruim e muito provavelmente um segfault em tempo de execução. O mesmo vale para A&a B&.
  • Gotcha : Transmitir de Derived to Base ou vice-versa cria uma nova cópia! Para pessoas provenientes de C # / Java, isso pode ser uma grande surpresa, porque o resultado é basicamente um objeto cortado, criado a partir do Derived.

dynamic_cast

  • dynamic_cast usa informações de tipo de tempo de execução para descobrir se a conversão é válida. Por exemplo, (Base*)a (Derived*)pode falhar se apontador não é, na verdade, do tipo derivado.
  • Isso significa que dynamic_cast é muito caro comparado ao static_cast!
  • Para A*a B*, se elenco é dynamic_cast então inválida retornará nullptr.
  • Para A&a B&se fundido é inválido, em seguida, dynamic_cast vai jogar exceção bad_cast.
  • Diferente de outras transmissões, há sobrecarga de tempo de execução.

const_cast

  • Enquanto static_cast pode fazer non-const para const, não pode ir ao contrário. O const_cast pode fazer nos dois sentidos.
  • Um exemplo em que isso é útil é a iteração em algum contêiner, como o 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.
  • Outro exemplo é quando você deseja implementar 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

  • Isso basicamente diz que pegue esses bytes nesse local de memória e pense nele como um objeto específico.
  • Por exemplo, você pode carregar 4 bytes de float a 4 bytes de int para ver como são os bits no float.
  • Obviamente, se os dados não estiverem corretos para o tipo, você poderá obter segfault.
  • Não há sobrecarga de tempo de execução para este elenco.
Shital Shah
fonte
Eu adicionei as informações do operador de conversão, mas há algumas outras coisas que devem ser corrigidas também e não me sinto tão confortável atualizando isso demais. Os itens são: 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.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 mutablepode ser uma opção melhor para implementar a constância lógica.
Adrian
1
@ Adrian, você está correto em todos os aspectos. A resposta é escrito para pessoas mais ou menos nível iniciante e eu não queria sobrecarregá-los com todas as outras complicações que vem com mutable, fundição cruz etc.
Shital Shah
16

Será que isso responde sua pergunta?

Eu nunca usei reinterpret_caste 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_castverificação em tempo de execução pode ser (mais segura) ou não (mais sobrecarga) o que você deseja (consulte msdn ).

andreas buykx
fonte
3
Eu usei reintrepret_cast para uma finalidade - obter os bits de um dobro (mesmo tamanho desde que na minha plataforma).
Joshua
2
reinterpret_cast é necessário, por exemplo, para trabalhar com objetos COM. CoCreateInstance () possui um parâmetro de saída do tipo void ** (o último parâmetro), no qual você passará o ponteiro declarado como, por exemplo, "INetFwPolicy2 * pNetFwPolicy2". Para fazer isso, você precisa escrever algo como reinterpret_cast <void **> (& pNetFwPolicy2).
Serge Rogatch
1
Talvez exista uma abordagem diferente, mas eu uso reinterpret_castpara 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); }
James Matta
Eu nunca usei reinterpret_cast, não há muitos usos para isso.
Pika, o Mago das Baleias,
Pessoalmente, eu só vi reinterpret_castusado 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_castsão usados ​​para transformar esses dados brutos no objeto.
ImaginHuman072889
15

Além das outras respostas até agora, aqui está um exemplo não óbvio, onde static_castnão é suficiente para que reinterpret_castseja 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_castfunciona 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);
Serge Rogatch
fonte
Não funcionaria algo como em &static_cast<void*>(pNetFwPolicy2)vez de static_cast<void**>(&pNetFwPolicy2)?
precisa saber é
9

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) vare 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.

Timmy_A
fonte
5

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 ++ :

  • Use static_cast como o equivalente a uma conversão no estilo C que valoriza a conversão, ou quando precisamos converter explicitamente um ponteiro de uma classe para sua superclasse.
  • Use const_cast para remover o qualificador const.
  • Use reinterpret_cast para fazer conversões inseguras de tipos de ponteiros de e para números inteiros e outros tipos de ponteiros. Use isso apenas se soubermos o que estamos fazendo e entendermos os problemas de alias.
Pankaj Kumar Thapa
fonte
3

static_castvs dynamic_castvs reinterpret_castinterna 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 nullptrque indica um downcast inválido.

    Portanto, se o seu código não conseguir verificar isso nullptre executar uma ação válida sem interrupção, você deve apenas usar em static_castvez 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_castcompilações in debug (-NDEBUG ) e usar o static_castcontrá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 setarchesta 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 Ddeve 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 Dcontém dentro dela uma estrutura de memória compatível com a de B1e 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 B2na memória:

b2s[1] = &d;

exceto que este possui a vtable em Dvez de B2e, 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_castfeito lá calculou corretamente o deslocamento da Destrutura de dados completa em 0x7fffffffc930 para a B2similar que está em 0x7fffffffffc940. Também inferimos que o que está entre 0x7fffffffc930 e 0x7fffffffcc40 é provavelmente os B1dados 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 B2para 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:

    • verifique se o ponteiro é NULL e, se sim, retorne NULL
    • caso contrário, subtraia 0x10 para alcançar o Dque não existe
  • dynamic_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 B2entrada para essa tabela e, em seguida, percorrer essa hierarquia de classes até encontrar a tabela de tabela para uma Dconversã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_castem um static_castem um projeto complexo reduziu o tempo de execução em 33%! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940este acredita em nós cegamente: dissemos que existe um Dendereç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 -O0montagem 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.

Ciro Santilli adicionou uma nova foto
fonte