web-dev-qa-db-fra.com

Réinterpréter_cast vs cast de style C

J'ai entendu dire que reinterpret_cast est défini par l'implémentation, mais je ne sais pas ce que cela signifie vraiment. Pouvez-vous donner un exemple de la façon dont cela peut mal tourner, et est-ce qu'il est préférable d'utiliser la fonte C-Style?

31
user103214

La distribution de style C n'est pas meilleure.

Il essaie simplement les différents transtypages de style C++ dans l'ordre, jusqu'à ce qu'il en trouve un qui fonctionne. Cela signifie que lorsqu'il agit comme un reinterpret_cast, il a exactement les mêmes problèmes qu'un reinterpret_cast. Mais en plus, il a ces problèmes:

  • Il peut faire beaucoup de choses différentes, et la lecture du code ne permet pas toujours de savoir quel type de transtypage sera appelé (cela peut se comporter comme un reinterpret_cast, un const_cast ou un static_cast, et ces actions sont très différentes)
  • Par conséquent, la modification du code environnant peut modifier le comportement de la distribution.
  • Il est difficile à trouver lors de la lecture ou de la recherche du code - reinterpret_cast est facile à trouver, ce qui est bien, car les conversions sont laides et doivent faire l'objet d'une attention particulière lors de leur utilisation. Inversement, une diffusion dans le style C (comme dans (int)42.0) est beaucoup plus difficile à trouver de manière fiable en recherchant

Pour répondre à l’autre partie de votre question, oui, reinterpret_cast est défini par l’implémentation. Cela signifie que lorsque vous l'utilisez pour convertir, par exemple, un int* en un float*, vous n'avez aucune garantie que le pointeur résultant pointera vers la même adresse. Cette partie est définie par l'implémentation. Mais si vous prenez les float* et reinterpret_cast résultants dans un int*, vous obtiendrez le pointeur d'origine. Cette partie est garantie.

Mais rappelez-vous que ceci est vrai que vous utilisiez reinterpret_cast ou une conversion de style C:

int i;
int* p0 = &i;

float* p1 = (float*)p0; // implementation-defined result
float* p2 = reinterpret_cast<float*>(p0); // implementation-defined result

int* p3 = (int*)p1; // guaranteed that p3 == p0
int* p4 = (int*)p2; // guaranteed that p4 == p0
int* p5 = reinterpret_cast<int*>(p1); // guaranteed that p5 == p0
int* p6 = reinterpret_cast<int*>(p2); // guaranteed that p6 == p0
31
jalf

C'est une implémentation définie en ce sens que standard ne prescrit pas (presque) la manière dont les valeurs de types doivent ressembler au niveau des bits, la manière dont l'espace adresse doit être structuré, etc. C'est donc vraiment une plateforme très spécifique pour les conversions telles que:

double d;
int &i = reinterpret_cast<int&>(d);

Cependant, comme le dit la norme

Il est destiné à ne pas surprendre ceux qui connaissent la structure d’adressage de la machine sous-jacente.

Donc, si vous savez ce que vous faites et à quoi cela ressemble, rien ne peut mal tourner.

La distribution de style C est quelque peu similaire dans le sens où elle peut effectuer reinterpret_cast, mais elle "essaie" en premier lieu static_cast et peut éliminer la qualification cv (alors que static_cast et reinterpret_cast ne le peuvent pas) et effectuer des conversions sans tenir compte du contrôle d'accès (voir 5.4)./4 en standard C++ 11). Par exemple.:

#include <iostream>

using namespace std;

class A { int x; };
class B { int y; };

class C : A, B { int z; };

int main()
{
  C c;

  // just type pun the pointer to c, pointer value will remain the same
  // only it's type is different.
  B *b1 = reinterpret_cast<B *>(&c);

  // perform the conversion with a semantic of static_cast<B*>(&c), disregarding
  // that B is an unaccessible base of C, resulting pointer will point
  // to the B sub-object in c.
  B *b2 = (B*)(&c);

  cout << "reinterpret_cast:\t" << b1 << "\n";
  cout << "C-style cast:\t\t" << b2 << "\n";
  cout << "no cast:\t\t" << &c << "\n";
}

et voici une sortie d'idéone:

 reinterpret_cast: 0xbfd84e78 
 cast de style C: 0xbfd84e7c
no cast: 0xbfd84e78 

notez que la valeur produite par reinterpret_cast est exactement identique à une adresse de 'c', alors que la conversion en style C a pour résultat un pointeur correctement décalé.

16

Il existe des raisons valables d'utiliser reinterpret_cast et, pour ces raisons, la norme définit en réalité ce qui se passe.

La première consiste à utiliser des types de pointeurs opaques, soit pour une API de bibliothèque, soit simplement pour stocker une variété de pointeurs dans un seul tableau (évidemment avec leur type). Vous êtes autorisé à convertir un pointeur en un entier de taille appropriée, puis à un pointeur. Ce sera exactement le même pointeur. Par exemple:

T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);

Dans ce code, il est garanti que c pointe sur l'objet b comme prévu. La reconversion en un type de pointeur différent est bien sûr indéfinie (en quelque sorte). 

Des conversions similaires sont autorisées pour les pointeurs de fonction et les pointeurs de fonction de membre, mais dans ce dernier cas, vous pouvez transtyper vers/depuis un autre pointeur de fonction de membre simplement pour avoir une variable de taille importante.

Le deuxième cas concerne l'utilisation de types de disposition standard. C'est quelque chose qui était pris en charge avant C++ 11 et qui est maintenant spécifié dans la norme. Dans ce cas, la norme traite reinterpret_cast comme une static_cast à void * puis une static_cast au type de destination. Ceci est très utilisé lorsque vous utilisez des protocoles binaires où les structures de données ont souvent les mêmes informations d'en-tête et vous permettent de convertir des types qui ont la même disposition mais diffèrent dans la structure de classes C++.

Dans les deux cas, vous devriez utiliser l'opérateur explicite reinterpret_cast plutôt que le C-Style. Bien que le style C fasse normalement la même chose, il risque d’être soumis à des opérateurs de conversion surchargés.

6
edA-qa mort-ora-y

C++ a des types, et la seule façon dont ils convertissent normalement entre eux est par des opérateurs de conversion bien définis que vous écrivez. En général, c'est tout ce dont vous avez besoin et que vous devriez utiliser pour écrire vos programmes.

Parfois, cependant, vous souhaitez réinterpréter les bits qui représentent un type en quelque chose d'autre. Ceci est généralement utilisé pour des opérations de très bas niveau et n'est pas quelque chose que vous devriez normalement utiliser. Pour ces cas, vous pouvez utiliser reinterpret_cast.

C'est une implémentation définie car le standard C++ ne dit pas grand chose sur la façon dont les choses doivent être disposées en mémoire. Cela est contrôlé par votre implémentation spécifique de C++. De ce fait, le comportement de reinterpret_cast dépend de la manière dont votre compilateur affiche les structures en mémoire et de la façon dont il implémente reinterpret_cast.

Les conversions en style C sont assez similaires à reinterpret_casts, mais elles ont beaucoup moins de syntaxe et ne sont pas recommandées. On pense que le casting est par nature une opération laide et qu’elle nécessite une syntaxe laide pour informer le programmeur que quelque chose de douteux se passe.

Un exemple simple de comment cela pourrait mal tourner:

std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;

Le comportement de ce programme n'est pas défini - un compilateur peut faire tout ce qu'il veut pour cela. Très probablement, vous auriez un crash lorsque le destructeur de string est appelé, mais qui sait! Cela pourrait simplement corrompre votre pile et provoquer un crash dans une fonction non liée.

4
Ayjay

Les transtypages reinterpret_cast et c-style sont définis par l'implémentation et font presque la même chose. Les différences sont:
1. reinterpret_cast ne peut pas supprimer constness. Par exemple : 

const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );

émettra une erreur:

error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers  

2. Si vous utilisez reinterpret_cast, il est facile de trouver les endroits où vous l'avez fait. Il n'est pas possible de faire avec des casts de style c

2
BЈовић

C-style jette parfois un objet de type non puni, tel que (unsigned int)-1, convertit parfois la même valeur dans un format différent, tel que (double)42, peut parfois faire de même, comme par exemple comment (void*)0xDEADBEEF réinterprète des bits mais que (void*)0 est garanti. Constante de pointeur null, qui n'a pas nécessairement la même représentation d'objet que (intptr_t)0 et demande très rarement au compilateur de faire quelque chose comme shoot_self_in_foot_with((char*)&const_object);.

C’est d’habitude tout va bien, mais lorsque vous voulez transtyper une double en un uint64_t, vous voulez parfois la valeur et parfois les bits. Si vous connaissez le langage C, vous savez lequel des casts de style C, mais il est plus agréable d’avoir une syntaxe différente pour les deux.

Bjarne Stroustrup, dans ses directives, recommandait reinterpret_cast dans un autre contexte: si vous voulez taper le punaise d'une manière que le langage ne définit pas par un static_cast, il vous suggère de le faire avec quelque chose comme reinterpret_cast<double&>(uint64) plutôt que les autres méthodes. Ce sont tous des comportements non définis, mais cela rend très explicite ce que vous faites et que vous le faites exprès. Lire un membre d'un syndicat différent de celui que vous avez écrit en dernier ne le fait pas.

0
Davislor