¿Cuándo usar reinterpret_cast?

460

Estoy algo confundido con la aplicabilidad de reinterpret_castfrente static_cast. Por lo que he leído, las reglas generales son usar reparto estático cuando los tipos se pueden interpretar en tiempo de compilación, de ahí la palabra static. Este es el reparto que el compilador de C ++ usa internamente también para los lanzamientos implícitos.

reinterpret_casts son aplicables en dos escenarios:

  • convertir tipos enteros a tipos de puntero y viceversa
  • convertir un tipo de puntero a otro. La idea general que tengo es que esto no es portátil y debe evitarse.

Cuando estoy un poco confundido es un uso que necesito, estoy llamando a C ++ desde C y el código C debe retener el objeto C ++, por lo que básicamente contiene un void*. ¿Qué reparto se debe usar para convertir entre el tipo void *y el de clase?

He visto el uso de ambos static_casty reinterpret_cast? Aunque, por lo que he estado leyendo, parece que statices mejor ya que el reparto puede suceder en tiempo de compilación. ¿Aunque dice usar reinterpret_castpara convertir de un tipo de puntero a otro?

Aprender
fuente
99
reinterpret_castno sucede en tiempo de ejecución. Ambas son declaraciones en tiempo de compilación. De en.cppreference.com/w/cpp/language/reinterpret_cast : "A diferencia de static_cast, pero como const_cast, la expresión reinterpret_cast no se compila con ninguna instrucción de la CPU. Es puramente una directiva del compilador que le indica al compilador que trate la secuencia de bits (representación de objeto) de expresión como si tuviera el tipo new_type ".
Cris Luengo
@HeretoLearn, ¿es posible agregar las piezas de código relevantes del archivo * .c y * .cpp? Creo que puede mejorar la exposición de la pregunta.
OrenIshShalom

Respuestas:

443

El estándar C ++ garantiza lo siguiente:

static_castAl utilizar un puntero hacia y desde, se void*conserva la dirección. Es decir, a continuación a, by ctodos apuntan a la misma dirección:

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

reinterpret_castsolo garantiza que si lanza un puntero a un tipo diferente y luego reinterpret_castvuelve al tipo original , obtendrá el valor original. Entonces en lo siguiente:

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

ay ccontienen el mismo valor, pero el valor de bno está especificado. (en la práctica, generalmente contendrá la misma dirección que ay c, pero eso no se especifica en el estándar y puede que no sea cierto en máquinas con sistemas de memoria más complejos).

Para lanzar hacia y desde void*, se static_castdebe preferir.

jalf
fuente
18
Me gusta el hecho de que 'b' no está definido. Te impide hacer cosas tontas con él. Si lanza algo a otro tipo de puntero, está pidiendo problemas y el hecho de que no pueda depender de él lo hace más cuidadoso. Si hubiera utilizado static_cast <> arriba, ¿para qué sirve la 'b'?
Martin York
3
Pensé que reinterpret_cast <> garantizaba el mismo patrón de bits. (que no es lo mismo que un puntero válido a otro tipo).
Martin York
37
el valor de bya no se especifica en C ++ 11 cuando se usa reinterpret_cast. Y en C ++ 03 se prohibió hacer un elenco de int*to (aunque los compiladores no implementaron eso y no era práctico, por lo tanto, se cambió para C ++ 11). void*reinterpret_cast
Johannes Schaub - litb
55
En realidad, esto no responde a la pregunta de "cuándo usar reinterpret_cast".
einpoklum
66
@LokiAstari Creo que no especificado no te impide hacer cosas tontas. Solo te detiene cuando recuerdas que no está especificado. Gran diferencia. Personalmente no me gusta sin especificar. Demasiado para recordar.
Helin Wang
158

Un caso cuando reinterpret_castes necesario es cuando se interactúa con tipos de datos opacos. Esto ocurre con frecuencia en las API de proveedores sobre las cuales el programador no tiene control. Aquí hay un ejemplo artificial en el que un proveedor proporciona una API para almacenar y recuperar datos globales arbitrarios:

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

Para usar esta API, el programador debe enviar VendorGlobalUserDatay volver sus datos . static_castno funcionará, uno debe usar 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;
}

A continuación se muestra una implementación artificial de la API de muestra:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
jwfearn
fuente
77
Sí, ese es el único uso significativo de reinterpret_cast que se me ocurre.
jalf
8
Esta puede ser una pregunta tardía, pero ¿por qué no utiliza la API del proveedor void*para eso?
Xeo
19
@Xeo No usan void * porque pierden (algunas) verificaciones de tipo en tiempo de compilación.
jesup
44
Un caso de uso práctico de los tipos de datos "opacos" es cuando desea exponer una API a C pero escribe la implementación en C ++. ICU es un ejemplo de una biblioteca que hace esto en varios lugares. Por ejemplo, en la API del verificador de falsificación, se tratan punteros de tipo USpoofChecker*, donde USpoofCheckerhay una estructura vacía. Sin embargo, debajo del capó, cada vez que pasa un USpoofChecker*, se somete reinterpret_casta un tipo interno de C ++.
sffc
@sffc ¿por qué no exponer el tipo de estructura C al usuario?
Gupta
101

La respuesta corta: si no sabe lo que reinterpret_castsignifica, no lo use. Si lo necesitará en el futuro, lo sabrá.

Respuesta completa:

Consideremos los tipos de números básicos.

Cuando convierte, por ejemplo, int(12)a unsigned float (12.0f)su procesador, debe invocar algunos cálculos, ya que ambos números tienen una representación de bits diferente. Esto es lo que static_castsignifica.

Por otro lado, cuando llama a reinterpret_castla CPU no invoca ningún cálculo. Simplemente trata un conjunto de bits en la memoria como si tuviera otro tipo. Entonces, cuando convierte int*a float*esta palabra clave, el nuevo valor (después de desreferenciar el puntero) no tiene nada que ver con el valor anterior en significado matemático.

Ejemplo: es cierto quereinterpret_castno es portátil debido a una razón: el orden de bytes (endianness). Pero esta es a menudo sorprendentemente la mejor razón para usarlo. Imaginemos el ejemplo: tiene que leer el número binario de 32 bits del archivo, y sabe que es big endian. Su código debe ser genérico y funciona correctamente en sistemas big endian (por ejemplo, algunos ARM) y little endian (por ejemplo, x86). Por lo tanto, debe verificar el orden de los bytes. Es bien conocido en tiempo de compilación, por lo que puede escribir una constexprfunción: puede escribir una función para lograr esto:

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

Explicación: la representación binaria dexin memory podría ser0000'0000'0000'0001(big) o0000'0001'0000'0000(little endian). Después de reinterpretar el byte bajo elppuntero podría ser respectivamente0000'0000o0000'0001. Si usa fundición estática, siempre lo será0000'0001, sin importar qué endianness se esté utilizando.

EDITAR:

En la primera versión hice una función de ejemplo is_little_endianpara ser constexpr. Se compila bien en el nuevo gcc (8.3.0) pero el estándar dice que es ilegal. El compilador clang se niega a compilarlo (lo cual es correcto).

jaskmar
fuente
1
Buen ejemplo! Reemplazaría short para uint16_t y unsigned char para uint8_t para que sea menos oscuro para los humanos.
Jan Turoň
@ JanTuroň cierto, no podemos suponer que shorttoma 16 bits en la memoria. Corregido
jaskmar
1
El ejemplo está mal. reinterpret_cast no está permitido en las funciones constexpr
Michael Veksler
1
En primer lugar, este código es rechazado tanto por el último clang (7.0.0) como por gcc (8.2.0). Lamentablemente no encontré la limitación en el lenguaje formal. Todo lo que pude encontrar fue social.msdn.microsoft.com/Forums/vstudio/en-US/…
Michael Veksler
2
Más específicamente, en.cppreference.com/w/cpp/language/constant_expression (elemento 16) establece claramente que reinterpret_cast no se puede usar en una expresión constante. Consulte también github.com/cplusplus/draft/blob/master/papers/N3797.pdf (5.19 expresiones constantes) páginas125-126 que excluye explícitamente reinterpret_cast. Entonces 7.1.5 El elemento 5 del especificador constexpr (página 146) * Para una función constexpr sin plantilla ni valores predeterminados ... si no existen valores de argumento tales que ... podría ser una subexpresión evaluada de una expresión constante central (5.19 ), el programa está mal formado *
Michael Veksler
20

El significado de reinterpret_castno está definido por el estándar C ++. Por lo tanto, en teoría a reinterpret_castpodría bloquear su programa. En la práctica, los compiladores intentan hacer lo que esperas, que es interpretar los bits de lo que estás pasando como si fueran del tipo al que estás enviando. Si sabes qué hacen los compiladores que vas a usar, reinterpret_cast puedes usarlo, pero decir que es portátil sería mentiroso.

Para el caso que describe, y prácticamente cualquier caso en el que pueda considerar reinterpret_cast, puede usar static_castu otra alternativa en su lugar. Entre otras cosas, el estándar tiene esto que decir sobre lo que puede esperar de static_cast(§5.2.9):

Un valor de tipo "puntero a cv void" se puede convertir explícitamente en un puntero a tipo de objeto. Un valor de tipo puntero a objeto convertido en "puntero a cv void" y de vuelta al tipo de puntero original tendrá su valor original.

Entonces, para su caso de uso, parece bastante claro que el comité de estandarización pretendía que lo usara static_cast.

flodin
fuente
55
No es exactamente lo que bloquea su programa. El estándar ofrece algunas garantías sobre reinterpret_cast. Simplemente no tantos como la gente suele esperar.
jalf
1
No si lo usas correctamente. Es decir, reinterpret_cast de A a B a A es perfectamente seguro y está bien definido. Pero el valor de B no está especificado, y sí, si confía en eso, podrían suceder cosas malas. Pero el yeso en sí es lo suficientemente seguro, siempre y cuando solo lo use de la manera que lo permite el estándar. ;)
jalf
55
lol, sospecho que reinterpret_crash podría bloquear tu programa. Pero reinterpret_cast no lo hará. ;)
jalf
55
<irony> Lo probé en mi compilador, y de alguna manera, se negó a compilar reinterpret_crash. De ninguna manera un error del compilador me impedirá bloquear mi programa de reinterpretación. ¡Informaré un error lo antes posible! </irony>
paercebal
18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
12

Un uso de reinterpret_cast es si desea aplicar operaciones bit a bit a flotantes (IEEE 754). Un ejemplo de esto fue el truco Fast Inverse Square-Root:

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

Trata la representación binaria del flotador como un número entero, lo desplaza a la derecha y lo resta de una constante, reduciendo a la mitad y negando el exponente. Después de volver a convertir a un flotador, se somete a una iteración de Newton-Raphson para que esta aproximación sea más exacta:

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;
}

Esto se escribió originalmente en C, por lo que utiliza los moldes de C, pero el elenco análogo de C ++ es reinterpret_cast.

Adam P. Goucher
fuente
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
El estándar dice que este es un comportamiento indefinido: en.cppreference.com/w/cpp/language/reinterpret_cast (bajo "alias de tipo")
Cris Luengo
@CrisLuengo Si sustituyo todo reinterpret_castpor memcpy, ¿sigue siendo UB?
sandthorn
@sandthorn: esto es UB de acuerdo con el estándar, pero si funciona para su arquitectura, no se preocupe. Este truco está bien, supongo, para cualquier compilador para arquitecturas Intel. No podría funcionar según lo previsto (o incluso bloquearse) en otras arquitecturas; por ejemplo, podría ser posible que los flotadores y los largos se almacenen en compartimentos de memoria separados (no es que yo sepa de tal arquitectura, es solo un argumento ...) . memcpyDefinitivamente lo haría legal.
Cris Luengo
2
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

Traté de concluir y escribí un elenco seguro simple usando plantillas. Tenga en cuenta que esta solución no garantiza lanzar punteros en una función.

Sasha Zezulinsky
fuente
1
¿Qué? ¿Por qué molestarse? Esto es precisamente lo que reinterpret_castya hace en esta situación: "Un puntero de objeto puede convertirse explícitamente en un puntero de objeto de un tipo diferente. [72] Cuando un valor v de tipo de puntero de objeto se convierte en el tipo de puntero de objeto" puntero a cv T ", el resultado es static_cast<cv T*>(static_cast<cv void*>(v))". - N3797.
underscore_d
En cuanto a la c++2003norma que puedo no encontrar que reinterpret_castlo hacestatic_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky
1
De acuerdo, es cierto, pero no me importa una versión de hace 13 años, y la mayoría de los codificadores tampoco deberían hacerlo (como es probable) si pueden evitarla. Las respuestas y comentarios realmente deberían reflejar el último estándar disponible a menos que se especifique lo contrario ... en mi humilde opinión. De todos modos, supongo que el Comité sintió la necesidad de agregar esto explícitamente después de 2003. (porque IIRC, era lo mismo en C ++ 11)
subrayado_d
Antes que C++03lo fuera C++98. Toneladas de proyectos utilizaron C ++ antiguo en lugar de C. portátil. A veces hay que preocuparse por la portabilidad. Por ejemplo, debe admitir el mismo código en Solaris, AIX, HPUX, Windows. En lo que respecta a la dependencia y portabilidad del compilador es complicado. Entonces, un buen ejemplo de la introducción de un infierno de portabilidad es usar un reinterpret_casten su código
Sasha Zezulinsky
De nuevo, si como yo estás contento de limitarte solo a las plataformas que juegan bien con la última y mejor versión del lenguaje, tu objeción es discutible.
underscore_d
1

Primero tienes algunos datos en un tipo específico como int aquí:

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

Entonces desea acceder a la misma variable que otro tipo como flotante: puede decidir entre

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 que se usa la misma memoria que un tipo diferente. Por lo tanto, podría convertir representaciones binarias de flotantes como tipo int como arriba en flotantes. 0x80000000 es -0 por ejemplo (la mantisa y el exponente son nulos pero el signo, el msb, es uno. Esto también funciona para dobles y dobles largos.

OPTIMIZE: Creo que reinterpret_cast se optimizaría en muchos compiladores, mientras que el c-casting se realiza mediante pointeraritmética (el valor debe copiarse en la memoria, porque los punteros no pueden apuntar a registros de CPU).

NOTA: ¡En ambos casos, debe guardar el valor emitido en una variable antes de lanzarlo! Esta macro podría ayudar:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
cmdLP
fuente
Es cierto que "significa que se usa la misma memoria como un tipo diferente", pero está restringido a un par específico de tipos. En su ejemplo reinterpret_castforma intque float&es un comportamiento indefinido.
jaskmar
1

Una razón para usar reinterpret_castes cuando una clase base no tiene una vtable, pero una clase derivada sí. En ese caso, static_casty reinterpret_castdará como resultado diferentes valores de puntero (este sería el caso atípico mencionado por jalf arriba ). Solo como descargo de responsabilidad, no estoy afirmando que esto sea parte del estándar, sino la implementación de varios compiladores generalizados.

Como ejemplo, tome el siguiente código:

#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;
}

Lo que genera algo como:

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

En todos los compiladores que probé (MSVC 2015 y 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - ver godbolt para los últimos 3 ) el resultado del static_castdifiere del de reinterpret_cast2 por 4 (4 para MSVC). El único compilador que advirtió sobre la diferencia fue el sonido metálico, con:

17:16: advertencia: 'reinterpret_cast' de la clase 'B *' a su base en un desplazamiento distinto de cero 'A *' se comporta de manera diferente a 'static_cast' [-Wreinterpret-base-class]
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
17:16: nota: use 'static_cast' para ajustar el puntero correctamente mientras se eleva
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~
static_cast

Una última advertencia es que si la clase base no tiene miembros de datos (por ejemplo, el int i;) entonces clang, gcc e icc devuelven la misma dirección para reinterpret_castque para static_cast, mientras que MSVC todavía no.

Avi Ginsburg
fuente
1

Aquí hay una variante del programa de Avi Ginsburg que ilustra claramente la propiedad reinterpret_castmencionada por Chris Luengo, flodin y cmdLP: que el compilador trata la ubicación de memoria apuntada como si fuera un objeto del nuevo 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);
}

Lo que da como resultado una salida como esta:

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.

Se puede ver que el objeto B se construye en la memoria como datos específicos de B primero, seguido por el objeto A incrustado. El static_castdevuelve correctamente la dirección del objeto A incrustado, y el puntero creado por static_castcorrectamente da el valor del campo de datos. El puntero generado por la ubicación de la memoria de reinterpret_casttrata bcomo si fuera un objeto A simple, por lo que cuando el puntero intenta obtener el campo de datos, devuelve algunos datos específicos de B como si fueran los contenidos de este campo.

Un uso de reinterpret_castes convertir un puntero a un entero sin signo (cuando los punteros y los enteros sin signo son del mismo tamaño):

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

TRPh
fuente
-6

Respuesta rápida: use static_castsi se compila, de lo contrario recurra a reinterpret_cast.

Marius K
fuente
-16

¡Lea las preguntas frecuentes ! Mantener datos de C ++ en C puede ser arriesgado.

En C ++, un puntero a un objeto se puede convertir void *sin ninguna conversión . Pero no es cierto al revés. Necesitaría un static_castpara recuperar el puntero original.

Dirkgently
fuente