Quand utiliser reinterpret cast?

Je suis peu confondu avec l'applicabilité de reinterpret_cast vs static_cast. D'après ce que j'ai lu, les règles générales consistent à utiliser un cast statique lorsque les types peuvent être interprétés au moment de la compilation, d'où le mot static. C'est le cast que le compilateur C++ utilise en interne pour les casts implicites également.

reinterpret_casts sont applicables dans deux scénarios, convertir des types entiers en types de pointeurs et vice versa ou pour convertir un type de pointeur en un autre. L'idée générale que je reçois est que c'est unportable et devrait être éviter.

Où je suis un peu confus est une utilisation dont j'ai besoin, j'appelle C++ à partir de C et le code C doit s'accrocher à l'objet c++ Donc fondamentalement il contient un void*. Quel cast doit être utilisé pour convertir entre le void * et le type de classe?

J'ai vu l'utilisation de deux static_cast et reinterpret_cast? Bien que d'après ce que j'ai lu, il semble que static est meilleur car le casting peut se produire au moment de la compilation? Bien qu'il soit dit d'utiliser reinterpret_cast pour convertir d'un type de pointeur à un autre?

369
demandé sur MD XF 2009-02-21 19:12:39

10 réponses

La norme c++ garantit ce qui suit:

static_casting un pointeur vers et depuis void* conserve l'adresse. C'est-à-dire que dans ce qui suit, a, b et c pointent tous vers la même adresse:

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

reinterpret_cast ne garantit que si vous lancez un pointeur vers un type différent, puis reinterpret_cast vers le type d'origine , Vous obtenez la valeur d'origine. Donc, dans ce qui suit:

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

A et c contiennent la même valeur, mais la valeur de b n'est pas spécifiée. (dans la pratique, il sera contiennent généralement la même adresse que a et c, mais cela n'est pas spécifié dans la norme, et cela peut ne pas être vrai sur les machines avec des systèmes de mémoire plus complexes.)

Pour lancer vers et depuis void*, static_cast devrait être préféré.

362
répondu jalf 2011-11-22 17:46:42

Un cas où reinterpret_cast est nécessaire est lors de l'interface avec des types de données opaques. Cela se produit fréquemment dans les API du fournisseur sur lesquelles le programmeur n'a aucun contrôle. Voici un exemple artificiel où un fournisseur fournit une API pour stocker et récupérer des données globales arbitraires:

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

Pour utiliser cette API, le programmeur doit convertir ses données en VendorGlobalUserData et inversement. static_cast ne fonctionnera pas, il faut utiliser 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;
}

Voici une implémentation artificielle de L'exemple D'API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
126
répondu jwfearn 2014-12-02 21:16:06

La réponse courte: Si vous ne savez pas ce que reinterpret_cast signifie, Ne l'utilisez pas. Si vous en aurez besoin à l'avenir, vous le saurez.

Réponse Complète:

Considérons les types de nombres de base.

Lorsque vous convertissez par exemple int(12) en unsigned float (12.0f), votre processeur doit invoquer des calculs car les deux nombres ont une représentation de bits différente. C'est quoi static_cast signifie.

D'autre part, lorsque vous appelez reinterpret_cast l' CPU n'invoque aucun calcul. Il traite juste un ensemble de bits dans la mémoire comme s'il avait un autre type. Ainsi, lorsque vous convertissez int* en float* avec ce mot-clé, la nouvelle valeur (après le déréférencement du pointeur) n'a rien à voir avec l'ancienne valeur au sens mathématique.

exemple: Il est vrai que reinterpret_cast n'est pas portable pour une raison-l'ordre des octets (endianness). Mais c'est souvent étonnamment, la meilleure raison de l'utiliser. Imaginons l'exemple: vous avez à lisez le numéro binaire 32 bits à partir du fichier, et vous savez que c'est big endian. Votre code doit être générique et fonctionne correctement sur les systèmes big endian (par exemple ARM) et little endian (par exemple x86). Donc, vous devez vérifier l'ordre des octets. Il est bien connu au moment de la compilation, donc vous pouvez écrire constexpr function:

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

Explication: la représentation binaire de x dans la mémoire pourrait être 0000'0000'0000'0001 (gros) ou 0000'0001'0000'0000 (little endian). Après réinterpret-casting l'octet sous p pointeur pourrait être respectivement 0000'0000 ou 0000'0001. Si vous utilisez static-casting, il sera toujours 0000'0001, peu importe ce que endianness est utilisé.

50
répondu jaskmar 2018-08-28 09:28:57

La signification de reinterpret_cast n'est pas définie par la norme c++. Par conséquent, en théorie, un reinterpret_cast pourrait planter votre programme. En pratique, les compilateurs essaient de faire ce que vous attendez, c'est-à-dire d'interpréter les bits de ce que vous transmettez comme s'ils étaient le type vers lequel vous lancez. Si vous connaissez les compilateurs que vous allez utiliser faire avec reinterpret_cast vous pouvez l'utiliser, mais de là à dire que c'est portable serait mentir.

Pour le cas que vous décrivez, et à peu près tous les cas où vous pourriez envisager reinterpret_cast, Vous pouvez utiliser static_cast ou une autre alternative à la place. Entre autres choses la norme a ceci à dire sur ce que vous pouvez attendre de static_cast (§5.2.9):

Une valeur R de type "pointeur vers CV void" peut être explicitement convertie en un pointeur vers un type d'objet. Une valeur de type pointeur vers objet convertie en "pointeur vers CV void" et retour au type de pointeur d'origine aura sa valeur d'origine.

Donc, pour votre cas, il semble assez clair que la normalisation Comité destiné à vous d'utiliser static_cast.

16
répondu flodin 2009-02-21 16:52:25

Une utilisation de reinterpret_cast est si vous voulez appliquer des opérations au niveau du BIT aux flottants (IEEE 754). Un exemple de ceci était L'astuce de racine carrée Inverse rapide:

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

Il traite la représentation binaire du flottant comme un entier, le déplace vers la droite et le soustrait d'une constante, réduisant ainsi de moitié et annulant l'exposant. Après la conversion en flotteur, il est soumis à un Newton-Raphson itération pour rendre cette approximation plus exacte:

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

Cela a été écrit à l'origine en C, donc utilise des conversions C, mais le cast c++ analogue est le reinterpret_cast.

10
répondu Adam P. Goucher 2016-07-16 12:32:13

Vous pouvez utiliser reinterprete_cast pour vérifier l'héritage au moment de la compilation.
Regardez ici: Utiliser reinterpret_cast pour vérifier l'héritage au moment de la compilation

2
répondu Martin R. 2017-05-23 12:34:50
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

J'ai essayé de conclure et j'ai écrit un cast sûr simple en utilisant des modèles. Notez que cette solution ne garantit pas la conversion de pointeurs sur une fonction.

0
répondu Sasha Zezulinsky 2013-06-27 15:52:14

Vous avez D'abord des données dans un type spécifique comme int ici:

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

Ensuite, vous voulez accéder à la même variable qu'un autre type comme float: Vous pouvez choisir entre

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

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

Ou

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

//this could be used in c and cpp

Bref: cela signifie que la même mémoire est utilisée comme un type différent. Vous pouvez donc Convertir les représentations binaires de flotteurs en tant que type int comme ci-dessus en flotteurs. 0x80000000 est -0 par exemple (la mantisse et l'exposant sont nuls mais le signe, le msb, est un. Cela fonctionne également pour les doubles et longs doubles.

OPTIMIZE: je pense que reinterpret_cast serait optimisé dans de nombreux compilateurs, tandis que le C-casting est fait par pointerarithmetic (la valeur doit être copiée dans la mémoire, car les pointeurs ne peuvent pas pointer vers les registres cpu).

NOTE: dans les deux cas, vous devez enregistrer la valeur casted dans une variable avant de la lancer! Cette macro pourrait aider:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
0
répondu cmdLP 2017-02-23 19:51:47

Réponse rapide: Utilisez static_cast s'il compile, sinon recourir à reinterpret_cast.

-3
répondu Marius K 2017-03-13 01:20:52

Lisez la FAQ ! Conserver des données C++ en C peut être risqué.

En C++, un pointeur vers un objet peut être converti void * sans les plâtres. Mais ce n'est pas vrai dans l'autre sens. Vous auriez besoin d'un static_cast pour récupérer le pointeur d'origine.

-14
répondu dirkgently 2017-03-13 01:21:54