623 votes

Quand utiliser reinterpret_cast?

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

Les reinterpret_casts sont applicables dans deux scénarios :

  • convertir les types entiers en types pointeur et vice versa
  • convertir un type pointeur en un autre. L'idée générale que j'ai est que c'est non portable et que cela devrait être évité.

où je suis un peu confus, c'est dans un cas d'utilisation dont j'ai besoin. J'appelle du C++ depuis du C et le code C doit conserver l'objet C++, donc il a essentiellement un void*. Quelle conversion devrait être utilisée entre le void * et le type Classe ?

J'ai vu l'utilisation à la fois de static_cast et de reinterpret_cast. Bien que d'après ce que j'ai lu, il semble que static soit préférable car la conversion peut se faire au moment de la compilation ? Bien que cela dise d'utiliser reinterpret_cast pour convertir d'un type pointeur à un autre ?

28 votes

reinterpret_cast ne se produit pas à l'exécution. Ce sont toutes les deux des déclarations au moment de la compilation. De en.cppreference.com/w/cpp/language/reinterpret_cast : "Contrairement à static_cast, mais comme const_cast, l'expression reinterpret_cast ne se compile pas en instructions CPU. C'est uniquement une directive du compilateur qui indique au compilateur de traiter la séquence de bits (représentation d'objet) de l'expression comme si elle avait le type new_type."

0 votes

@HeretoLearn, est-il possible d'ajouter les morceaux de code pertinents du fichier *.c et *.cpp ? Je pense que cela peut améliorer l'exposition de la question.

571voto

jalf Points 142628

La norme C++ garantit ce qui suit :

static_cast d'un pointeur vers et depuis void* préserve l'adresse. Autrement dit, dans le code suivant, a, b et c pointent tous vers la même adresse :

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

reinterpret_cast ne garantit que si vous castez un pointeur vers un type différent, _et que vous le reinterpret_cast ensuite vers le type d'origine_, vous obtenez la valeur d'origine. Ainsi, dans le code suivant :

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

a et c contiennent la même valeur, mais la valeur de b est non spécifiée. (en pratique, elle contiendra 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 des machines avec des systèmes mémoire plus complexes.)

Pour effectuer des castings vers et depuis void*, static_cast devrait être privilégié.

18 votes

J'aime le fait que 'b' soit indéfini. Cela vous empêche de faire des choses stupides avec lui. Si vous castez quelque chose vers un autre type de pointeur, vous demandez des problèmes et le fait que vous ne pouvez pas vous fier à cela vous rend plus prudent. Si vous aviez utilisé static_cast<> ci-dessus, à quoi sert le 'b' de toute façon?

5 votes

J'ai pensé que reinterpret_cast<> garantissait le même schéma de bits. (qui n'est pas la même chose qu'un pointeur valide vers un autre type).

1 votes

@Martin - reinterpret_cast<> n'est pas garanti de donner le même motif de bits. "La correspondance effectuée par reinterpret_cast<> est définie par l'implémentation." (C++03 5.3.10). Cependant, la norme note que "elle est censée ne pas surprendre".

220voto

jwfearn Points 8813

Un cas où reinterpret_cast est nécessaire est lors de l'interfaçage avec des types de données opaques. Cela se produit fréquemment dans les APIs de fournisseurs sur lesquelles le programmeur n'a aucun contrôle. Voici un exemple fabriqué 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 leurs données en VendorGlobalUserData et les reconvertir de nouveau. static_cast ne fonctionnera pas, il faut utiliser reinterpret_cast :

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

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

int main() {
    MyUserData u;

        // stocker les données globales
    VendorGlobalUserData d1;
//  d1 = &u;                                          // erreur de compilation
//  d1 = static_cast(&u);       // erreur de compilation
    d1 = reinterpret_cast(&u);  // ok
    VendorSetUserData(d1);

        // faire d'autres choses...

        // récupérer les données globales
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // erreur de compilation
//  p = static_cast(d2);                // erreur de compilation
    p = reinterpret_cast(d2);           // ok

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

Ci-dessous se trouve une implémentation fabriquée de l'API d'exemple :

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

11 votes

Oui, c'est à peu près la seule utilisation significative de reinterpret_cast à laquelle je peux penser.

13 votes

Il s'agit peut-être d'une question tardive, mais pourquoi l'API du vendeur n'utilise-t-elle pas void* pour cela ?

29 votes

@Xeo Ils n'utilisent pas void * car ils perdent ainsi (certain) contrôle des types au moment de la compilation.

21voto

flodin Points 2489

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 à quoi vous vous attendez, c'est-à-dire d'interpréter les bits de ce que vous passez comme si c'était le type auquel vous lancez. Si vous savez ce que les compilateurs que vous allez utiliser font avec reinterpret_cast, vous pouvez l'utiliser, mais dire que c'est portable serait mentir.

Pour le cas que vous mentionnez, et à peu près tout autre cas où vous pourriez envisager d'utiliser reinterpret_cast, vous pouvez utiliser static_cast ou une autre alternative à la place. Entre autres choses, la norme dit ceci à propos de ce que vous pouvez attendre de static_cast (§5.2.9):

Une rvalue de type "pointeur vers cv void" peut être convertie explicitement en un pointeur vers un objet. Une valeur de type pointeur vers un objet convertie en "pointeur vers cv void" et reconvertie dans le type de pointeur original conservera sa valeur d'origine.

Donc, pour votre cas d'utilisation, il semble assez clair que le comité de normalisation a prévu que vous utilisiez static_cast.

7 votes

Ne fait pas planter ton programme. La norme offre quelques garanties concernant reinterpret_cast. Juste pas autant que les gens s'attendent souvent.

1 votes

Eh bien, le reinterpret_cast lui-même ne planterait probablement pas, mais il pourrait renvoyer un résultat erroné qui, lorsque vous essayez de l'utiliser, pourrait provoquer un plantage.

2 votes

Pas si vous l'utilisez correctement. Autrement dit, reinterpret_cast de A à B à A est parfaitement sûr et bien défini. Mais la valeur de B est indéterminée, et oui, si vous vous basez là-dessus, des choses désagréables pourraient se produire. Mais la conversion en elle-même est suffisamment sûre, tant que vous l'utilisez uniquement de la manière autorisée par la norme. ;)

1voto

zezulinsky Points 41
modèle 
outType safe_cast(inType pointeur)
{
    void* temp = static_cast(pointeur);
    return static_cast(temp);
}

J'ai essayé de conclure et ai écrit une simple conversion sécurisée en utilisant des modèles. Notez que cette solution ne garantit pas de convertir des pointeurs sur des fonctions.

2 votes

Quoi ? Pourquoi s'embêter ? C'est précisément ce que reinterpret_cast fait déjà dans cette situation : "Un pointeur d'objet peut être converti explicitement en un pointeur d'objet d'un type différent.[72] Lorsqu'une valeur de type pointeur d'objet prvalue v est convertie en un pointeur d'objet du type "pointeur vers cv T", le résultat est static_cast(static_cast(v))." -- N3797.

0 votes

En ce qui concerne la norme c++2003, je ne peux PAS trouver que reinterpret_cast fait static_cast(static_cast(v))

1 votes

OK, vrai, mais je ne me soucie pas d'une version de il y a 13 ans, et la plupart des codeurs ne devraient pas non plus, s'ils peuvent l'éviter. Les réponses et les commentaires doivent vraiment refléter la dernière norme disponible, sauf indication contraire... À mon humble avis. Quoi qu'il en soit, je suppose que le Comité a jugé nécessaire d'ajouter explicitement cela après 2003. (car je me souviens bien, c'était pareil en C++11)

-5voto

acidzombie24 Points 28569

Jamais. Vous n'avez jamais besoin de l'utiliser. Vous pouvez utiliser static_cast pour convertir de void* (voici une démo http://ideone.com/Ic04w). N'utilisez JAMAIS reinterpret_cast.

C'est destiné à l'accès matériel. Etes-vous en train d'écrire un pilote de périphérique ? Je suis assez sûr que non. Donc écrire ce qui suit est quelque chose que vous ne ferez pas. Vous êtes autorisé à stocker des pointeurs dans des entiers suffisamment grands pour les contenir et nous savons tous que nous pouvons afficher des entiers à l'utilisateur ou les écrire dans un fichier à des fins de diagnostic. Peut-être pour le débogage en arithmétique des pointeurs ou pour les comparer à un autre mais cela est également peu probable.

#define pNomRegistreMateriel reinterpret_cast(0x11223344)
//plus tard dans le code
nomRegistreMateriel = *pNomRegistreMateriel; //n'oubliez pas que nomRegistreMateriel n'est pas volatile

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X