¿Cuándo utilizar reinterpret_cast?

Resuelto HeretoLearn asked hace 15 años • 11 respuestas

Estoy un poco confundido con la aplicabilidad de reinterpret_castvs. static_castPor lo que he leído, las reglas generales son usar conversión estática cuando los tipos se pueden interpretar en tiempo de compilación, de ahí la palabra static. Esta es la conversión que el compilador de C++ usa internamente también para conversiones implícitas.

reinterpret_casts son aplicables en dos escenarios:

  • convertir tipos de 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.

Donde estoy un poco confundido es en un uso que necesito: estoy llamando a C++ desde C y el código C necesita retener el objeto C++, por lo que básicamente contiene un archivo void*. ¿Qué conversión se debe utilizar para convertir entre el void *tipo y la Clase?

¿He visto el uso de ambos static_casty reinterpret_cast? Aunque por lo que he estado leyendo parece que statices mejor ya que la conversión puede ocurrir en el momento de la compilación. ¿ Aunque dice usarlo reinterpret_castpara convertir de un tipo de puntero a otro?

HeretoLearn avatar Feb 21 '09 23:02 HeretoLearn
Aceptado

El estándar C++ garantiza lo siguiente:

static_castAl escribir un puntero hacia y desde void*se conserva la dirección. Es decir, a continuación, ay btodos capuntan 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_castlo vuelve 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 ccontiene el mismo valor, pero el valor de bno está especificado. (En la práctica, normalmente contendrá la misma dirección que ay c, pero eso no está especificado en el estándar y puede no ser cierto en máquinas con sistemas de memoria más complejos).

Para transmitir desde y hacia void*, static_castse debe preferir.

jalf avatar Feb 21 '2009 16:02 jalf

Un caso en el que reinterpret_castes necesario es cuando se interactúa con tipos de datos opacos. Esto ocurre con frecuencia en API de proveedores sobre las cuales el programador no tiene control. A continuación se muestra 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 utilizar esta API, el programador debe enviar sus datos hacia VendorGlobalUserDatay desde atrás. 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 avatar Feb 21 '2009 19:02 jwfearn

La respuesta corta: si no sabes lo que reinterpret_castsignifica, no lo uses. Si lo necesitarás en el futuro, lo sabrás.

Respuesta completa:

Consideremos los tipos de números básicos.

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

Por otro lado, cuando llama reinterpret_casta la CPU no invoca ningún cálculo. Simplemente trata un conjunto de bits en la memoria como si tuvieran otro tipo. Entonces, cuando convierte int*con float*esta palabra clave, el nuevo valor (después de eliminar la referencia del puntero) no tiene nada que ver con el valor anterior en significado matemático (ignorando el hecho de que es un comportamiento indefinido leer este valor).

Tenga en cuenta que leer o modificar valores después de reinterprt_cast'ing suele ser un comportamiento indefinido . En la mayoría de los casos, debe usar un puntero o una referencia a std::byte(a partir de C++ 17) si desea lograr la representación en bits de algunos datos; casi siempre es una operación legal. Otros tipos "seguros" son chary unsigned char, pero yo diría que no deberían usarse para ese propósito en C++ moderno ya que std::bytetiene una mejor semántica.

Ejemplo: es cierto quereinterpret_castno es portátil por una razón: el orden de los bytes (endianidad). Pero, sorprendentemente, esta suele ser la mejor razón para utilizarlo. Imaginemos el ejemplo: tienes que leer un número binario de 32 bits de un archivo y sabes que es big endian. Su código debe ser genérico y debe funcionar correctamente en sistemas big endian (por ejemplo, algunos ARM) y little endian (por ejemplo, x86). Entonces debes verificar el orden de los bytes. Es bien conocido en tiempo de compilación por lo que puedes escribir constexprfunciones: Puedes 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 binariaxen la memoria podría ser0000'0000'0000'0001(grande) o0000'0001'0000'0000(little endian). Después de reinterpretar la conversión, el byte bajopel puntero podría ser respectivamente0000'0000o0000'0001. Si usa conversión estática, siempre será0000'0001, sin importar qué endianidad se use.

EDITAR:

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

Mariusz Jaskółka avatar Apr 07 '2017 08:04 Mariusz Jaskółka

El significado de reinterpret_castno está definido por el estándar C++. Por lo tanto, en teoría, un reinterpret_castprograma podría bloquearse. En la práctica, los compiladores intentan hacer lo que usted espera, que es interpretar los bits de lo que está pasando como si fueran del tipo al que está transmitiendo. Si sabes qué hacen los compiladores que vas a utilizar reinterpret_cast puedes usarlo, pero decir que es portátil sería mentir.

Para el caso que usted describe, y prácticamente cualquier caso en el que pueda considerar reinterpret_cast, puede utilizar static_castalguna otra alternativa. Entre otras cosas, la norma dice lo siguiente sobre lo que se puede esperar static_cast(§5.2.9):

Un rvalue 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 a “puntero a cv void” y de nuevo al tipo de puntero original tendrá su valor original.

Entonces, para su caso de uso, parece bastante claro que el comité de estandarización tenía la intención de que usted usara static_cast.

flodin avatar Feb 21 '2009 16:02 flodin