Quando utilizzare reinterpret_cast?


460

Sto po 'confuso con l'applicabilità del reinterpret_castvs static_cast. Da quello che ho letto le regole generali sono di usare il cast statico quando i tipi possono essere interpretati in fase di compilazione da qui la parola static. Questo è il cast che il compilatore C ++ usa internamente anche per cast impliciti.

reinterpret_castsono applicabili in due scenari:

  • converte i tipi interi in tipi di puntatori e viceversa
  • converti un tipo di puntatore in un altro. L'idea generale che ottengo è che questo non è giustificabile e dovrebbe essere evitato.

Dove sono un po 'confuso è un uso di cui ho bisogno, sto chiamando C ++ da C e il codice C deve rimanere sull'oggetto C ++, quindi sostanzialmente contiene a void*. Quale cast dovrebbe essere usato per convertire tra il tipo void *e il tipo di classe?

Ho visto l'uso di entrambi static_caste reinterpret_cast? Anche se da quello che ho letto sembra che staticsia meglio come può accadere il cast in fase di compilazione? Anche se dice di usare reinterpret_castper convertire da un tipo di puntatore a un altro?


9
reinterpret_castnon accade in fase di esecuzione. Sono entrambe dichiarazioni in fase di compilazione. Da en.cppreference.com/w/cpp/language/reinterpret_cast : "A differenza di static_cast, ma come const_cast, l'espressione reinterpret_cast non viene compilata in nessuna istruzione della CPU. È puramente una direttiva del compilatore che indica al compilatore di trattare la sequenza di bit (rappresentazione dell'oggetto) dell'espressione come se avesse il tipo new_type. "
Cris Luengo,

@HeretoLearn, è possibile aggiungere i pezzi di codice rilevanti dal file * .c e * .cpp? Penso che possa migliorare l'esposizione della domanda.
OrenIshShalom il

Risposte:


443

Lo standard C ++ garantisce quanto segue:

static_castil puntatore da e verso void*conserva l'indirizzo. Cioè, nel seguito a, be ctutti puntano allo stesso indirizzo:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_castgarantisce solo che se si esegue il cast di un puntatore a un tipo diverso e quindi reinterpret_castsi torna al tipo originale , si ottiene il valore originale. Quindi nel seguente:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

ae ccontengono lo stesso valore, ma il valore di bnon è specificato. (in pratica conterrà in genere lo stesso indirizzo di ae c, ma ciò non è specificato nello standard e potrebbe non essere vero su macchine con sistemi di memoria più complessi.)

Per la fusione da e verso void*, static_castdovrebbe essere preferito.


18
Mi piace il fatto che "b" non sia definito. Ti impedisce di fare cose stupide con esso. Se lanci qualcosa su un altro tipo di puntatore, stai chiedendo problemi e il fatto che non puoi dipendere da questo ti rende più attento. Se avessi usato static_cast <> sopra a che cosa serve la 'b'?
Martin York,

3
Ho pensato che reinterpret_cast <> garantisse lo stesso modello di bit. (che non equivale a un puntatore valido per un altro tipo).
Martin York,

37
il valore di bnon è più specificato in C ++ 11 durante l'utilizzo reinterpret_cast. E in C ++ 03 era vietato eseguire un cast di int*to (anche se i compilatori non lo implementavano ed era poco pratico, quindi è stato modificato per C ++ 11). void*reinterpret_cast
Johannes Schaub - litb

55
Questo in realtà non risponde alla domanda "quando usare reinterpret_cast".
einpoklum,

6
@LokiAstari Penso che non specificato non ti impedisca di fare cose sciocche. Ti ferma solo quando ricordi che non è specificato. Differenza enorme. Personalmente non mi piace non specificato. Troppo da ricordare.
Helin Wang,

158

Un caso in cui reinterpret_castè necessario è quando si interfaccia con tipi di dati opachi. Ciò si verifica frequentemente nelle API del fornitore su cui il programmatore non ha alcun controllo. Ecco un esempio inventato in cui un fornitore fornisce un'API per l'archiviazione e il recupero di dati globali arbitrari:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

Per utilizzare questa API, il programmatore deve trasmettere VendorGlobalUserDatae reinserire i propri dati . static_castnon funzionerà, è necessario utilizzare reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

Di seguito è un'implementazione forzata dell'API di esempio:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

7
Sì, è l'unico uso significativo di reinterpret_cast che mi viene in mente.
jalf

8
Questa potrebbe essere una domanda tardiva, ma perché l'API del fornitore non la utilizza void*?
Xeo,

19
@Xeo Non usano void * perché poi perdono (alcuni) controlli del tipo al momento della compilazione.
jesup

4
Un caso d'uso pratico di tipi di dati "opachi" è quando si desidera esporre un'API in C ma scrivere l'implementazione in C ++. L'ICU è un esempio di una libreria che lo fa in diversi punti. Ad esempio, nell'API dello spoof checker, hai a che fare con puntatori di tipo USpoofChecker*, dove USpoofCheckerè presente una struttura vuota. Tuttavia, sotto il cofano, ogni volta che passi a USpoofChecker*, subisce reinterpret_castun tipo C ++ interno.
Sffc,

@sffc perché non esporre il tipo di struttura C all'utente?
Gupta,

101

La risposta breve: se non sai cosa reinterpret_castrappresenta, non usarlo. Se ne avrai bisogno in futuro, lo saprai.

Risposta completa:

Consideriamo i tipi di numeri di base.

Quando si converte ad esempio int(12)nel unsigned float (12.0f)proprio processore, è necessario richiamare alcuni calcoli poiché entrambi i numeri hanno una rappresentazione bit diversa. Questo è ciò che static_castrappresenta.

D'altra parte, quando si chiama reinterpret_castla CPU non viene richiamato alcun calcolo. Tratta solo una serie di bit nella memoria come se avesse un altro tipo. Quindi, quando si converte int*in float*con questa parola chiave, il nuovo valore (dopo il dereferecing del puntatore) non ha nulla a che fare con il vecchio valore in significato matematico.

Esempio: è vero chereinterpret_castnon è portatile a causa di un motivo: l'ordine dei byte (endianness). Ma questo è spesso sorprendentemente il miglior motivo per usarlo. Immaginiamo l'esempio: devi leggere il numero binario a 32 bit dal file e sai che è big endian. Il codice deve essere generico e funziona correttamente su sistemi big endian (ad es. Alcuni ARM) e little endian (ad es. X86). Quindi devi controllare l'ordine dei byte. È noto in fase di compilazione, quindi è possibile scrivere una constexprfunzione: è possibile scrivere una funzione per raggiungere questo obiettivo:

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

Spiegazione: la rappresentazione binaria dixin memory potrebbe essere0000'0000'0000'0001(big) o0000'0001'0000'0000(little endian). Dopo aver reinterpretato il casting, il byte sotto ilppuntatore potrebbe essere rispettivamente0000'0000o0000'0001. Se usi il casting statico, lo sarà sempre0000'0001, indipendentemente dall'endianness.

MODIFICARE:

Nella prima versione feci esempio la funzione is_little_endiandi essere constexpr. Si compila bene sul più recente gcc (8.3.0) ma lo standard dice che è illegale. Il compilatore clang rifiuta di compilarlo (che è corretto).


1
Bell'esempio! Vorrei sostituire l'abbreviazione di uint16_t e il carattere unsigned per uint8_t per renderlo meno oscuro per l'uomo.
Jan Turoň,

@ JanTuroň vero, non possiamo supporre che shortrichieda 16 bit in memoria. Corretto.
Jaskmar,

1
L'esempio è sbagliato. reinterpret_cast non è consentito nelle funzioni constexpr
Michael Veksler

1
Prima di tutto, questo codice è rifiutato sia dall'ultimo clang (7.0.0) che da gcc (8.2.0). Purtroppo non ho trovato la limitazione nel linguaggio formale. Tutto quello che ho potuto trovare era social.msdn.microsoft.com/Forums/vstudio/en-US/…
Michael Veksler

2
Più specificamente, en.cppreference.com/w/cpp/language/constant_expression (elemento 16) afferma chiaramente che reinterpret_cast non può essere utilizzato in un'espressione costante. Guarda anche github.com/cplusplus/draft/blob/master/papers/N3797.pdf (5.19 espressioni costanti) pagine 125-126 che esclude esplicitamente reinterpret_cast. Quindi 7.1.5 Voce 5 dell'identificatore constexpr (pagina 146) * Per una funzione constexpr non-template, non predefinita ... se non esistono valori di argomento tali che ... potrebbe essere una sottoespressione valutata di un'espressione costante core (5.19 ), il programma è mal formato *
Michael Veksler

20

Il significato di reinterpret_castnon è definito dallo standard C ++. Quindi, in teoria, un reinterpret_castprogramma potrebbe andare in crash. In pratica, i compilatori cercano di fare ciò che ti aspetti, ovvero interpretare le parti di ciò che stai passando come se fossero il tipo a cui stai eseguendo il casting. Se sai che cosa faranno i compilatori che utilizzerai, reinterpret_cast puoi usarlo, ma dire che è portatile mentirebbe.

Per il caso che descrivi, e praticamente ogni caso in cui potresti prendere in considerazione reinterpret_cast, puoi usare invece static_casto qualche altra alternativa. Tra le altre cose, lo standard ha questo da dire su cosa ci si può aspettare da static_cast(§5.2.9):

Un valore di tipo "pointer to cv void" può essere esplicitamente convertito in un puntatore al tipo di oggetto. Un valore di tipo pointer in object convertito in "pointer to cv void" e di nuovo nel tipo di puntatore originale avrà il suo valore originale.

Quindi, per il tuo caso d'uso, sembra abbastanza chiaro che il comitato di standardizzazione è destinato a te static_cast.


5
Non interrompere completamente il programma. Lo standard offre alcune garanzie su reinterpret_cast. Solo non quante persone si aspettano spesso.
jalf

1
Non se lo usi correttamente. Cioè, reinterpret_cast da A a B a A è perfettamente sicuro e ben definito. Ma il valore di B non è specificato, e sì, se ti affidi a questo, potrebbero accadere cose brutte. Ma il cast stesso è abbastanza sicuro, fintanto che lo hai usato solo nel modo consentito dallo standard. ;)
jalf

55
lol, sospetto che reinterpret_crash potrebbe davvero arrestare il tuo programma. Ma reinterpret_cast no. ;)
jalf

5
<irony> L'ho provato sul mio compilatore e, in qualche modo, si è rifiutato di compilare reinterpret_crash. In nessun modo un bug del compilatore mi impedirà di bloccare il mio programma di reinterpretazione.
Segnalerò

18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }

12

Un uso di reinterpret_cast è se si desidera applicare operazioni bit a bit ai float (IEEE 754). Un esempio di questo è stato il trucco Fast Inverse Square-Root:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

Tratta la rappresentazione binaria del galleggiante come un numero intero, lo sposta a destra e lo sottrae da una costante, dimezzando e negando l'esponente. Dopo la riconversione in float, viene sottoposto a un'iterazione di Newton-Raphson per rendere questa approssimazione più esatta:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Questo è stato originariamente scritto in C, quindi usa i cast C, ma l'analogo cast C ++ è reinterpret_cast.


1
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))- ideone.com/6S4ijc
Orwellophile,

1
Lo standard afferma che si tratta di un comportamento indefinito: en.cppreference.com/w/cpp/language/reinterpret_cast (sotto "aliasing di tipo")
Cris Luengo

@CrisLuengo Se sostituisco tutto reinterpret_castcon memcpy, è ancora UB?
sandthorn,

@sandthorn: questo è UB secondo lo standard, ma se funziona per la tua architettura, non preoccuparti. Questo trucco è OK, presumo, per qualsiasi compilatore per architetture Intel. Non potrebbe funzionare come previsto (o addirittura arrestarsi in modo anomalo) su altre architetture - ad esempio potrebbe essere possibile che float e long siano memorizzati in compartimenti di memoria separati (non che io conosca tale architettura, è solo un argomento ...) . memcpylo renderebbe sicuramente legale.
Cris Luengo,


2
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

Ho provato a concludere e ho scritto un semplice cast sicuro usando i template. Si noti che questa soluzione non garantisce il cast di puntatori su una funzione.


1
Che cosa? Perché preoccuparsi? Questo è esattamente ciò che reinterpret_castgià fa in questa situazione: "Un puntatore oggetto può essere esplicitamente convertito in un puntatore oggetto di un tipo diverso. [72] Quando un valore v di tipo puntatore oggetto viene convertito nel tipo di puntatore oggetto" pointer to cv T ", il risultato è static_cast<cv T*>(static_cast<cv void*>(v))". - N3797.
underscore_d

Per quanto riguarda la c++2003serie posso non constatare che reinterpret_castfastatic_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky

1
OK, vero, ma non mi interessa una versione di 13 anni fa, e la maggior parte dei programmatori non dovrebbe (se è probabile) evitarlo. Le risposte e i commenti dovrebbero davvero riflettere l'ultimo standard disponibile se non diversamente specificato ... IMHO. Ad ogni modo, immagino che il Comitato abbia sentito la necessità di aggiungere esplicitamente questo dopo il 2003. (poiché IIRC, era lo stesso in C ++ 11)
underscore_d

Prima C++03lo era C++98. Moltissimi progetti hanno utilizzato il vecchio C ++ anziché il portatile C. A volte devi preoccuparti della portabilità. Ad esempio, devi supportare lo stesso codice su Solaris, AIX, HPUX, Windows. Dove si tratta di dipendenza del compilatore e portabilità è difficile. Quindi un buon esempio dell'introduzione di un inferno di portabilità è l'uso di un reinterpret_castnel tuo codice
Sasha Zezulinsky,

di nuovo, se come me sei felice di limitarti solo alle piattaforme che funzionano bene con l'ultima e la più grande versione della lingua, la tua obiezione è un punto controverso.
underscore_d

1

Per prima cosa hai alcuni dati in un tipo specifico come int qui:

int x = 0x7fffffff://==nan in binary representation

Quindi si desidera accedere alla stessa variabile di un altro tipo come float: è possibile decidere tra

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

o

float y = *(float*)&(x);

//this could be used in c and cpp

BREVE: significa che la stessa memoria viene utilizzata come tipo diverso. Quindi è possibile convertire le rappresentazioni binarie di float come tipo int come sopra in float. 0x80000000 è -0 per esempio (la mantissa e l'esponente sono nulli ma il segno, il msb, è uno. Questo funziona anche per i doppi e i doppi lunghi.

OTTIMIZZA: Penso che reinterpret_cast sarebbe ottimizzato in molti compilatori, mentre il c-casting è fatto da pointerarithmetic (il valore deve essere copiato nella memoria, perché i puntatori non possono puntare ai registri).

NOTA: in entrambi i casi è necessario salvare il valore cast in una variabile prima del cast! Questa macro potrebbe aiutare:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

È vero che "significa che la stessa memoria viene utilizzata come tipo diverso" ma è limitata a una specifica coppia di tipi. Nel reinterpret_castmodulo di esempio intto float&è un comportamento indefinito.
Jaskmar,

1

Un motivo da usare reinterpret_castè quando una classe base non ha una vtable, ma una classe derivata ha. In tal caso, static_caste reinterpret_castrisulteranno in valori di puntatore diversi (questo sarebbe il caso atipico menzionato da jalf sopra ). Proprio come un disclaimer, non sto affermando che questo fa parte dello standard, ma l'implementazione di numerosi compilatori diffusi.

Ad esempio, prendi il codice qui sotto:

#include <cstdio>

class A {
public:
    int i;
};

class B : public A {
public:
    virtual void func() {  }
};

int main()
{
    B b;
    const A* a = static_cast<A*>(&b);
    const A* ar = reinterpret_cast<A*>(&b);

    printf("&b = %p\n", &b);
    printf(" a = %p\n", a);
    printf("ar = %p\n", ar);
    printf("difference = %ld\n", (long int)(a - ar));

    return 0;
}

Che produce qualcosa di simile:

& b = 0x7ffe10e68b38
a = 0x7ffe10e68b40
ar = 0x7ffe10e68b38
differenza = 2

In tutti i compilatori che ho provato (MSVC 2015 e 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - vedi godbolt per gli ultimi 3 ) il risultato del static_castdifferisce da quello del reinterpret_cast2 (4 per MSVC). L'unico compilatore per avvertire della differenza era clang, con:

17:16: avviso: 'reinterpret_cast' dalla classe 'B *' alla sua base con offset diverso da zero 'A *' si comporta diversamente da 'static_cast' [-Wreinterpret-base-class]
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
17:16: nota: usa 'static_cast' per regolare correttamente il puntatore durante l'upgrade di
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~
static_cast

Un'ultima avvertenza è che se la classe base non ha membri di dati (ad es. Il int i;), allora clang, gcc e icc restituiscono lo stesso indirizzo per reinterpret_castcome static_cast, mentre MSVC ancora non lo fa.


1

Ecco una variante del programma di Avi Ginsburg che illustra chiaramente la proprietà reinterpret_castmenzionata da Chris Luengo, flodin e cmdLP: che il compilatore tratta la posizione della memoria puntata come se fosse un oggetto del nuovo tipo:

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class A
{
public:
    int i;
};

class B : public A
{
public:
    virtual void f() {}
};

int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);

    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";

    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

Il che si traduce in output in questo modo:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0

&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978

&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

Si può vedere che l'oggetto B viene creato in memoria come dati specifici B prima, seguito dall'oggetto A incorporato. Il static_castrestituisce correttamente l'indirizzo dell'oggetto A incorporato, e il puntatore creato da static_castcorrettamente dà il valore del campo dati. Il puntatore generato dagli reinterpret_castossequib posizione della memoria come se fosse un semplice oggetto A, quindi quando il puntatore tenta di ottenere il campo dati restituisce alcuni dati specifici di B come se fosse il contenuto di questo campo.

Un uso di reinterpret_castè convertire un puntatore in un numero intero senza segno (quando i puntatori e i numeri interi senza segno hanno le stesse dimensioni):

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);


-6

Risposta rapida: utilizzare static_castse compilato, altrimenti ricorrere a reinterpret_cast.


-16

Leggi le FAQ ! Conservare i dati C ++ in C può essere rischioso.

In C ++, un puntatore a un oggetto può essere convertito void *senza cast. Ma non è vero il contrario. Avresti bisogno di a static_castper ripristinare il puntatore originale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.