web-dev-qa-db-fra.com

Pourquoi cette réinterprète_cast ne compile-t-elle pas?

Je comprends que reinterpret_cast est dangereux, je le fais juste pour le tester. J'ai le code suivant:

int x = 0;
double y = reinterpret_cast<double>(x);

Lorsque j'essaie de compiler le programme, cela me donne une erreur en disant

conversion non valide du type 'float' à le type 'double

Que se passe-t-il? Je pensais que reinterpret_cast était la distribution malveillante que vous pouviez utiliser pour convertir des pommes en sous-marins. Pourquoi cette distribution ne se compile-t-elle pas?

54
Vlad the Impala

En affectant y à la valeur renvoyée par la distribution, vous ne convertissez pas vraiment la valeur x, vous la convertissez. C’est-à-dire que y ne pointe pas sur x et ne prétend pas qu’elle pointe sur un float. La conversion construit une nouvelle valeur de type float et lui attribue la valeur de x. Il y a plusieurs façons de faire cette conversion en C++, parmi lesquelles:

int main()
{
    int x = 42;
    float f = static_cast<float>(x);
    float f2 = (float)x;
    float f3 = float(x);
    float f4 = x;
    return 0;
}

La seule différence réelle étant la dernière (une conversion implicite) générera un diagnostic du compilateur à des niveaux d'avertissement plus élevés. Mais ils font tous fonctionnellement la même chose - et dans de nombreux cas en fait la même chose, comme dans le même code machine.

Maintenant, si vous voulez vraiment prétendre que x est un float, alors vous voulez vraiment lancer x en procédant comme suit:

#include <iostream>
using namespace std;

int main()
{
    int x = 42;
    float* pf = reinterpret_cast<float*>(&x);
    (*pf)++;
    cout << *pf;
    return 0;
}

Vous pouvez voir à quel point c'est dangereux. En fait, lorsque je lance ceci sur ma machine, le résultat est 1, ce qui n'est décidément pas 42 + 1.

41
John Dibling

En C++, reinterpret_cast ne peut effectuer qu'un ensemble spécifique de conversions, explicitement répertorié dans la spécification du langage. En bref, reinterpret_cast ne peut effectuer que des conversions point à point et des conversions référence à référence (plus des conversions pointeur à entier et entier à pointeur). Ceci est conforme à l'intention exprimée dans le nom même de la distribution: il est destiné à être utilisé pour la réinterprétation du pointeur/de la référence.

Ce que vous essayez de faire n’est pas une réinterprétation. Si vous souhaitez réinterpréter une int en tant que double, vous devez le convertir en type de référence.

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

bien que la réinterprétation équivalente basée sur un pointeur soit probablement plus explicite

double y = *reinterpret_cast<double*>(&x); // same as above

Notez cependant que, bien que reinterpret_cast puisse convertir les types référence/pointeur, la tentative réelle de lecture des données par le biais de la référence/pointeur résultante produit un comportement non défini. 

Et dans tous les cas, bien entendu, cela n’a aucun sens sur une plate-forme avec int et double de taille différente (puisque dans le cas de double plus grand, vous lirez au-delà de la mémoire occupée par x).

En fin de compte, tout se résume à ce que vous tentiez de réaliser. Réinterprétation de la mémoire? Voir au dessus. Une sorte de conversion int à double plus significative? Si c'est le cas, reinterpret_cast ne vous aidera pas ici.

38
AnT

reinterpret_cast n'est pas une distribution générale. Selon la section 5.2.10.1 de la spécification C++ 03:

Les conversions pouvant être effectuées explicitement à l'aide de reinterpret_cast sont répertoriées ci-dessous. Aucune autre conversion ne peut être effectuée explicitement à l'aide de reinterpret_cast.

Et rien dans la liste ne décrit la conversion entre les types intégrale et à virgule flottante (ou entre les types intégraux, même si cela est illégal reinterpret_cast<long>(int(3));)

11
R Samuel Klatchko

Si vous essayez de convertir les bits de votre int en une représentation de double, vous devez convertir le address et non la valeur. Vous devez également vous assurer que les tailles correspondent:

uint64_t x = 0x4045000000000000;
double y = *reinterpret_cast<double *>(&x);
9
finnw

Le compilateur rejette ce que vous avez écrit comme non-sens car int et double peuvent être des objets de tailles différentes. Vous pouvez obtenir le même effet de cette façon, même si c'est dangereux:

int x = 0;
double y = *reinterpret_cast<double*>(&x);

Ceci est potentiellement dangereux car si x et y sont de tailles différentes (disons que int est de quatre octets et double de huit octets), alors lorsque vous déréférencerez les huit octets de mémoire de &x pour remplir y, vous aurez accès à quatre octets de x de ... tout ce qui vient ensuite dans la mémoire (peut-être le début de y, ou des ordures, ou quelque chose d'autre.)

Si vous voulez convertir un entier en double, utilisez un static_cast et il effectuera la conversion.

Si vous souhaitez accéder au modèle de bits de x, transformez-le en un type de pointeur commode (par exemple, byte*) et accédez à sizeof(int) / sizeof(byte):

byte* p = reinterpret_cast<byte*>(&x);
for (size_t i = 0; i < sizeof(int); i++) {
  // do something with p[i]
}
3
Dominic Cooney

Réinterpréter la conversion vous permet de réinterpréter un bloc de mémoire sous un type différent. Ceci doit être effectué sur des pointeurs ou des références :

int x = 1;
float & f = reinterpret_cast<float&>(x);
assert( static_cast<float>(x) != f );   // !!

L’autre chose est qu’il s’agit en fait d’une distribution très dangereuse, non seulement en raison de valeurs étranges générées par les résultats, ou de l’affirmation ci-dessus qui n’échoue pas, mais parce que si les types sont de tailles différentes, et que vous réinterprétez de 'source' à Les types de 'destination', toute opération sur le pointeur/référence réinterprété aura accès à sizeof(destination) octets. Si sizeof(destination)>sizeof(source), cela ira au-delà de la mémoire de variable réelle, risquant de tuer votre application ou d'écraser d'autres variables que la source ou la destination:

struct test {
   int x;
   int y;
};
test t = { 10, 20 };
double & d = reinterpret_cast<double&>( t.x );
d = 1.0/3.0;
assert( t.x != 10 ); // most probably at least.
asswet( t.y != 20 );

reinterpret_cast est le mieux utilisé pour les pointeurs. Ainsi, un pointeur sur un objet peut être transformé en "sous-marin".

De msdn :

L'opérateur reinterpret_cast peut être utilisé pour les conversions telles que char * à int * ou One_class * to Unrelated_class *, qui est intrinsèquement peu sûr.

Le résultat d'un reinterpret_cast ne peut être utilisé en toute sécurité pour quoi que ce soit autre que d'être renvoyé à son type d'origine. Les autres utilisations sont, à meilleur, non portable.

1
Alex B

L'approche de réinterprétation m'a conduit sur un chemin étrange avec des résultats inconstants. En fin de compte, j’ai trouvé que c’était bien mieux de se souvenir de la sorte!

double source = 0.0;
uint64_t dest;
memcpy(&dest, &source, sizeof(dest));
0
Evan Moran

Lancer un int sur un double ne nécessite pas de casting. Le compilateur effectuera l'assignation de manière implicite.

Reinterpret_cast est utilisé avec des pointeurs et des références, par exemple, en transformant un int * en un double *.

0
Matt Davis

C'est intéressant. Peut-être qu’il fait une conversion implicite d’int pour flotter avant d’essayer de doubler le casting. Les types int et float tendent à avoir la même taille en octets (selon votre système bien sûr).

0
Andy White