Quali sono gli usi corretti di:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Cast in stile C.
(type)value
- Cast di stile funzionale
type(value)
Come si decide quale utilizzare in quali casi specifici?
Quali sono gli usi corretti di:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Come si decide quale utilizzare in quali casi specifici?
Risposte:
static_cast
è il primo cast che dovresti provare a usare. Fa cose come conversioni implicite tra tipi (come int
to float
o pointer to void*
) e può anche chiamare funzioni di conversione esplicite (o implicite). In molti casi, static_cast
non è necessario dichiarare esplicitamente , ma è importante notare che la T(something)
sintassi è equivalente (T)something
e dovrebbe essere evitata (ne parleremo più avanti). A T(something, something_else)
è sicuro, tuttavia, e garantito per chiamare il costruttore.
static_cast
può anche eseguire il cast delle gerarchie ereditarie. Non è necessario quando si lancia verso l'alto (verso una classe base), ma quando si lancia verso il basso può essere utilizzato purché non passi attraverso l' virtual
eredità. Tuttavia, non esegue il controllo ed è un comportamento indefinito static_cast
ridurre una gerarchia a un tipo che non è effettivamente il tipo di oggetto.
const_cast
può essere usato per rimuovere o aggiungere const
a una variabile; nessun altro cast C ++ è in grado di rimuoverlo (nemmeno reinterpret_cast
). È importante notare che la modifica di un const
valore precedente non è definita solo se la variabile originale è const
; se lo usi per togliere const
un riferimento a qualcosa con cui non è stato dichiarato const
, è sicuro. Ciò può essere utile quando si sovraccaricano le funzioni dei membri basate const
, ad esempio. Può anche essere utilizzato per aggiungere const
a un oggetto, ad esempio per chiamare un sovraccarico della funzione membro.
const_cast
funziona anche allo stesso modo volatile
, sebbene sia meno comune.
dynamic_cast
è utilizzato esclusivamente per la gestione del polimorfismo. È possibile eseguire il cast di un puntatore o riferimento a qualsiasi tipo polimorfico a qualsiasi altro tipo di classe (un tipo polimorfico ha almeno una funzione virtuale, dichiarata o ereditata). Puoi usarlo per più di un semplice lancio verso il basso: puoi lanciare lateralmente o anche su un'altra catena. Il dynamic_cast
cercherà l'oggetto desiderato e tornare, se possibile. In caso contrario, verrà restituito nullptr
nel caso di un puntatore, oppure verrà lanciato std::bad_cast
nel caso di un riferimento.
dynamic_cast
ha alcune limitazioni, però. Non funziona se ci sono più oggetti dello stesso tipo nella gerarchia dell'ereditarietà (il cosiddetto "diamante temuto") e non stai usando l' virtual
ereditarietà. Inoltre può solo passare attraverso l'eredità pubblica - non riuscirà sempre a viaggiare attraverso protected
o private
eredità. Questo è raramente un problema, tuttavia, poiché tali forme di eredità sono rare.
reinterpret_cast
è il cast più pericoloso e dovrebbe essere usato con parsimonia. Trasforma un tipo direttamente in un altro, come ad esempio lanciare il valore da un puntatore a un altro o archiviare un puntatore in una int
o altre cose brutte. In gran parte, l'unica garanzia che ottieni reinterpret_cast
è che normalmente se restituisci il risultato al tipo originale, otterrai lo stesso valore esatto (ma non se il tipo intermedio è più piccolo del tipo originale). Esistono anche numerose conversioni che reinterpret_cast
non possono essere eseguite. Viene utilizzato principalmente per conversioni e manipolazioni di bit particolarmente strane, come trasformare un flusso di dati non elaborati in dati reali o archiviare i dati nei bit bassi di un puntatore in dati allineati.
Cast di tipo C e cast di tipo funzione sono calchi utilizzando (type)object
o type(object)
, rispettivamente, e sono funzionalmente equivalenti. Sono definiti come il primo dei seguenti che ha esito positivo:
const_cast
static_cast
(sebbene ignorando le restrizioni di accesso)static_cast
(vedi sopra), quindi const_cast
reinterpret_cast
reinterpret_cast
, poi const_cast
Può quindi essere usato come sostituto di altri cast in alcuni casi, ma può essere estremamente pericoloso a causa della capacità di devolvere in un reinterpret_cast
, e quest'ultimo dovrebbe essere preferito quando è necessario il cast esplicito, a meno che non si sia sicuri che static_cast
avrà successo o reinterpret_cast
fallirà . Anche allora, considera l'opzione più lunga ed esplicita.
I lanci di tipo C ignorano anche il controllo di accesso quando eseguono un static_cast
, il che significa che hanno la capacità di eseguire un'operazione che nessun altro cast può fare. Questo è principalmente un problema, e nella mia mente è solo un altro motivo per evitare i cast in stile C.
const
(nemmeno reinterpret_cast
)" ... davvero? Che dire reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
è spesso l'arma preferita quando si ha a che fare con una serie di tipi di dati opachi di un'API
Utilizzare dynamic_cast
per convertire puntatori / riferimenti all'interno di una gerarchia di ereditarietà.
Utilizzare static_cast
per conversioni di tipo ordinario.
Utilizzare reinterpret_cast
per la reinterpretazione a basso livello di modelli di bit. Utilizzare con estrema cautela.
Utilizzare const_cast
per gettare via const/volatile
. Evita questo a meno che non ti blocchi con un'API const-errata.
(Molte spiegazioni teoriche e concettuali sono state fornite sopra)
Di seguito sono riportati alcuni esempi pratici quando ho usato static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Fa riferimento anche a questo per comprendere la spiegazione: 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
funziona solo tra tipi con conversioni definite, relazione visibile per eredità o da / a void *
. Per tutto il resto, ci sono altri cast. reinterpret cast
a qualsiasi char *
tipo è consentito consentire la lettura della rappresentazione di qualsiasi oggetto - e uno dei pochi casi in cui tale parola chiave è utile, non un generatore dilagante di implementazione / comportamento indefinito. Ma questa non è considerata una conversione "normale", quindi non è consentita dal (di solito) molto conservatore static_cast
.
Potrebbe essere utile se conosci un po 'di interni ...
static_cast
static_cast
per loro.A
a B
, il costruttore delle static_cast
chiamate B
passa A
come parametro. In alternativa, A
potrebbe avere un operatore di conversione (ad es A::operator B()
.). Se B
non ha tale costruttore o A
non ha un operatore di conversione, viene visualizzato un errore di tempo di compilazione.A*
al B*
riesce sempre se A e B sono in gerarchia di ereditarietà (o nulla) altrimenti si ottiene errore di compilazione.A&
a B&
.dynamic_cast
(Base*)
per (Derived*)
può riuscire se il puntatore non è effettivamente di tipo derivato.A*
a B*
, se cast non è valido, dynamic_cast restituirà nullptr.A&
per B&
se cast è valido quindi dynamic_cast lancerà un'eccezione bad_cast.const_cast
set<T>
quali restituisce solo i suoi elementi come const per assicurarsi di non cambiare la sua chiave. Tuttavia, se il tuo intento è quello di modificare i membri non chiave dell'oggetto, allora dovrebbe essere ok. È possibile utilizzare const_cast per rimuovere la costanza.T& SomeClass::foo()
e const T& SomeClass::foo() const
. Per evitare la duplicazione del codice, è possibile applicare const_cast per restituire il valore di una funzione da un'altra.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.
Ottieni UB che può comportare un segfault in fase di esecuzione se sei fortunato. 2. I lanci dinamici possono essere utilizzati anche nel cross casting. 3. In alcuni casi i cast di costanti possono causare UB. L'uso mutable
può essere una scelta migliore per implementare la costanza logica.
mutable
, il casting incrociato ecc.
Ha questa risposta alla domanda?
Non ho mai usato reinterpret_cast
, e mi chiedo se incappare in un caso che ne abbia bisogno non è un odore di cattivo design. Nella base di codice su cui lavoro dynamic_cast
è molto usata. La differenza static_cast
è che a dynamic_cast
esegue un controllo di runtime che può (più sicuro) o meno (più overhead) essere ciò che si desidera (vedere msdn ).
reinterpret_cast
per estrarre pezzi di dati da un array. Ad esempio, se ho char*
un grande buffer pieno di dati binari compressi che devo spostare e ottenere singole primitive di vario tipo. Qualcosa del genere: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
, non ci sono molti usi per questo.
reinterpret_cast
usato solo per un motivo. Ho visto dati di oggetti grezzi archiviati in un tipo di dati "BLOB" in un database, quindi quando i dati vengono recuperati dal database, reinterpret_cast
vengono utilizzati per trasformare questi dati grezzi nell'oggetto.
Oltre alle altre risposte finora, qui è un esempio evidente dove static_cast
non è sufficiente, quindi reinterpret_cast
è necessario. Supponiamo che ci sia una funzione che in un parametro di output restituisce puntatori a oggetti di classi diverse (che non condividono una classe base comune). Un vero esempio di tale funzione è CoCreateInstance()
(vedi l'ultimo parametro, che è in realtà void**
). Supponiamo che tu richieda una particolare classe di oggetti da questa funzione, quindi conosci in anticipo il tipo per il puntatore (cosa che fai spesso per gli oggetti COM). In questo caso non è possibile eseguire il cast del puntatore al puntatore void**
con static_cast
: è necessario reinterpret_cast<void**>(&yourPointer)
.
Nel codice:
#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) );
Tuttavia, static_cast
funziona per semplici puntatori (non puntatori a puntatori), quindi il codice sopra può essere riscritto per evitare reinterpret_cast
(a un prezzo di una variabile aggiuntiva) nel modo seguente:
#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)
invece di static_cast<void**>(&pNetFwPolicy2)
?
Mentre altre risposte descrivono bene tutte le differenze tra i cast C ++, vorrei aggiungere una breve nota del perché non dovresti usare i cast in stile C (Type) var
e Type(var)
.
Per i principianti C ++ i cast in stile C sembrano essere l'operazione superset sui cast C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) e qualcuno potrebbe preferirli ai cast C ++ . In effetti il cast in stile C è il superset e il più breve da scrivere.
Il problema principale dei cast in stile C è che nascondono la vera intenzione dello sviluppatore del cast. I cast in stile C possono eseguire praticamente tutti i tipi di cast da cast normalmente sicuri eseguiti da static_cast <> () e dynamic_cast <> () a cast potenzialmente pericolosi come const_cast <> (), dove il modificatore const può essere rimosso in modo che le variabili const può essere modificato e reinterpret_cast <> () che può anche reinterpretare i valori interi nei puntatori.
Ecco il campione.
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.
Il motivo principale per cui i cast di C ++ sono stati aggiunti al linguaggio è stato quello di consentire a uno sviluppatore di chiarire le sue intenzioni - perché farà quel cast. Usando i cast in stile C che sono perfettamente validi in C ++, stai rendendo il tuo codice meno leggibile e più soggetto a errori soprattutto per gli altri sviluppatori che non hanno creato il tuo codice. Quindi, per rendere il tuo codice più leggibile ed esplicito, dovresti sempre preferire i cast C ++ rispetto ai cast in stile C.
Ecco una breve citazione dal libro di Bjarne Stroustrup (l'autore del C ++) The C ++ Programming Language 4th edition - page 302.
Questo cast in stile C è molto più pericoloso degli operatori di conversione nominati perché la notazione è più difficile da individuare in un programma di grandi dimensioni e il tipo di conversione previsto dal programmatore non è esplicito.
Per capire, consideriamo di seguito lo snippet di codice:
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 riga (4) viene compilata senza errori. Solo reinterpret_cast può essere utilizzato per convertire un puntatore a un oggetto in un puntatore a qualsiasi tipo di oggetto non correlato.
Uno da notare è: dynamic_cast non funzionerebbe in fase di esecuzione, tuttavia sulla maggior parte dei compilatori non riuscirà anche a compilare perché non ci sono funzioni virtuali nella struttura del puntatore da trasmettere, il che significa che dynamic_cast funzionerà solo con puntatori di classe polimorfici .
Quando utilizzare il cast C ++ :
static_cast
vs dynamic_cast
vs reinterpret_cast
internals visualizza su un downcast / upcast
In questa risposta, voglio confrontare questi tre meccanismi su un esempio concreto di upcast / downcast e analizzare cosa succede ai puntatori / memoria / assembly sottostanti per dare una comprensione concreta di come si confrontano.
Credo che questo darà una buona intuizione su come questi cast siano diversi:
static_cast
: esegue un offset dell'indirizzo in fase di runtime (basso impatto di runtime) e non verifica che un downcast sia corretto.
dyanamic_cast
: esegue lo stesso offset dell'indirizzo durante il runtime static_cast
, ma anche un costoso controllo di sicurezza che un downcast è corretto utilizzando RTTI.
Questo controllo di sicurezza consente di eseguire una query se un puntatore della classe base è di un determinato tipo in fase di esecuzione controllando un valore restituito nullptr
che indica un downcast non valido.
Pertanto, se il tuo codice non è in grado di verificarlo nullptr
e di intraprendere un'azione valida di non interruzione, dovresti semplicemente utilizzare static_cast
invece del cast dinamico.
Se un abort è l'unica azione che il tuo codice può intraprendere, forse vuoi solo abilitare dynamic_cast
in debug builds ( -NDEBUG
), e usare static_cast
altrimenti, ad esempio come fatto qui , per non rallentare le tue corse veloci.
reinterpret_cast
: non fa nulla in fase di esecuzione, nemmeno l'offset dell'indirizzo. Il puntatore deve puntare esattamente al tipo corretto, nemmeno una classe base funziona. In genere non lo si desidera a meno che non siano coinvolti flussi di byte non elaborati.
Considera il seguente esempio di codice:
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, esegui e disassembla 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
dove setarch
viene utilizzato per disabilitare ASLR per facilitare il confronto delle esecuzioni.
Uscita possibile:
&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
Ora, come menzionato in: https://en.wikipedia.org/wiki/Virtual_method_table per supportare in modo efficiente le chiamate al metodo virtuale, la struttura dei dati di memoria D
deve assomigliare a:
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
Il fatto chiave è che la struttura dei dati di memoria di D
contiene al suo interno una struttura di memoria compatibile con quella di B1
e quella B2
interna.
Pertanto giungiamo alla conclusione critica:
un upcast o downcast deve solo spostare il valore del puntatore di un valore noto al momento della compilazione
In questo modo, quando D
viene passato all'array del tipo di base, il cast di tipo calcola effettivamente quell'offset e punta a qualcosa che sembra esattamente un valido B2
in memoria:
b2s[1] = &d;
tranne che questo ha la vtable per D
invece di B2
, e quindi tutte le chiamate virtuali funzionano in modo trasparente.
Ora possiamo finalmente tornare al tipo di casting e all'analisi del nostro esempio concreto.
Dall'output stdout vediamo:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Pertanto, l'implicito static_cast
fatto lì ha calcolato correttamente l'offset dalla D
struttura dati completa a 0x7fffffffc930 a quella B2
simile che è a 0x7fffffffc940. Ne deduciamo anche che ciò che sta tra 0x7fffffffc930 e 0x7fffffffc940 è probabilmente i B1
dati e la vtable.
Quindi, nelle sezioni downcast, ora è facile capire come falliscono quelli non validi e perché:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: il compilatore ha appena aumentato 0x10 in fase di compilazione byte per provare a passare da B2
a al contenimentoD
Ma poiché b2s[0]
non era un D
, ora indica una regione di memoria indefinita.
Lo smontaggio è:
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)
quindi vediamo che GCC fa:
D
quale non esistedynamic_cast<D*>(b2s[0]) 0
: C ++ in realtà ha scoperto che il cast non era valido e restituito nullptr
!
Non è possibile farlo al momento della compilazione e lo confermeremo dallo smontaggio:
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)
Innanzitutto c'è un controllo NULL e restituisce NULL se l'ingresso è NULL.
Altrimenti, imposta alcuni argomenti in RDX, RSI e RDI e chiama __dynamic_cast
.
Non ho la pazienza di analizzarlo ulteriormente ora, ma come altri hanno detto, l'unico modo per farlo funzionare è __dynamic_cast
quello di accedere ad alcune strutture di dati in memoria RTTI extra che rappresentano la gerarchia di classi.
Deve quindi iniziare dalla B2
voce per quella tabella, quindi seguire questa gerarchia di classi fino a quando trova la vtable per un D
typecast b2s[0]
.
Ecco perché reinterpretare il cast è potenzialmente costoso! Ecco un esempio in cui una patch di una riga che converte a dynamic_cast
in static_cast
in un progetto complesso ha ridotto il tempo di esecuzione del 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
questo ci crede ciecamente: abbiamo detto che esiste un D
indirizzo b2s[1]
e il compilatore non esegue calcoli di offset.
Ma questo è sbagliato, poiché D è in realtà a 0x7fffffffc930, ciò che è a 0x7fffffffc940 è la struttura simile a B2 all'interno di D! Quindi si accede alla spazzatura.
Possiamo confermarlo dall'assemblea orrenda -O0
che sposta semplicemente il valore:
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)
Domande correlate:
Testato su Ubuntu 18.04 amd64, GCC 7.4.0.