Unions C ++ contre reinterpret_cast

D’après d’ autres questions StackOverflow et à la lecture du § 9.5.1 du projet de norme ISO / CEI C ++, le fait d’utiliser des unions pour effectuer une reinterpret_cast simultanée de données au format littéral constitue un comportement indéfini.

Considérons le code ci-dessous. L’objective est de prendre la valeur entière de 0xffff et de l’interpréter littéralement comme une série de bits en virgule flottante IEEE 754. ( La conversion binary montre visuellement comment cela se fait. )

 #include  using namespace std; union unionType { int myInt; float myFloat; }; int main() { int i = 0xffff; unionType u; u.myInt = i; cout << "size of int " << sizeof(int) << endl; cout << "size of float " << sizeof(float) << endl; cout << "myInt " << u.myInt << endl; cout << "myFloat " << u.myFloat << endl; float theFloat = *reinterpret_cast(&i); cout << "theFloat " << theFloat << endl; return 0; } 

La sortie de ce code, utilisant à la fois les compilateurs GCC et clang est attendue.

 size of int 4 size of float 4 myInt 65535 myFloat 9.18341e-41 theFloat 9.18341e-41 

Ma question est la suivante: la norme empêche-t-elle réellement la valeur de myFloat d’être déterministe? L’utilisation d’un reinterpret_cast meilleure en quelque sorte pour effectuer ce type de conversion?

La norme stipule ce qui suit au § 9.5.1:

Dans une union, au plus un des membres de données non statiques peut être actif à tout moment, c’est-à-dire que la valeur d’au plus un des membres de données non statiques peut être stockée dans une union à tout moment. […] La taille d’une union est suffisante pour contenir le plus grand de ses membres de données non statiques. Chaque membre de données non statique est alloué comme s’il s’agissait du seul membre d’une structure. Tous les membres de données non statiques d’un object d’union ont la même adresse.

La dernière phrase, garantissant que tous les membres non statiques ont la même adresse, semble indiquer que l’utilisation d’une union est garantie identique à celle d’un reinterpret_cast , mais la déclaration précédente concernant les membres actifs de données semble exclure cette garantie.

Alors quelle construction est la plus correcte?

Edit: en utilisant le compilateur icpc d’Intel, le code ci-dessus produit des résultats encore plus intéressants:

 $ icpc union.cpp $ ./a.out size of int 4 size of float 4 myInt 65535 myFloat 0 theFloat 0 

La raison pour laquelle il n’est pas défini est qu’il n’y a aucune garantie quant à la représentation exacte de la valeur de int et float . La norme C ++ ne dit pas qu’un float est stocké sous forme de nombre à virgule flottante simple précision IEEE 754. Qu’est-ce que la norme devrait dire exactement à propos du traitement d’un object int avec la valeur 0xffff tant que float ? Il ne dit rien d’autre que le fait qu’il soit indéfini.

Cependant, dans la pratique, c’est le but de reinterpret_cast – de dire au compilateur d’ignorer tout ce qu’il sait sur les types d’objects et de vous faire confiance que cet int est en fait un float . Il est presque toujours utilisé pour le jiggery-pokery au niveau du bit spécifique à la machine. La norme C ++ ne vous garantit simplement rien une fois que vous le faites. À ce stade, il vous appartient de comprendre exactement ce que votre compilateur et votre machine font dans cette situation.

Ceci est vrai pour les approches union et reinterpret_cast . Je suggère que reinterpret_cast est “meilleur” pour cette tâche, car cela clarifie l’intention. Cependant, garder votre code bien défini est toujours la meilleure approche.

Ce n’est pas un comportement indéfini. C’est un comportement défini par l’implémentation. Le premier signifie que de mauvaises choses peuvent arriver. L’autre signifie que ce qui va arriver doit être défini par la mise en œuvre.

Reinterpret_cast viole la règle de crénelage ssortingct. Donc, je ne pense pas que cela fonctionnera de manière fiable. Le truc des syndicats est ce que les gens appellent le type-punning et est généralement autorisé par les compilateurs. Les membres de gcc documentent le comportement du compilateur: http://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit_002dfields-implementation.html#Structures-unions-enumerations-and-bit_002dfields-implementation.html

Je pense que cela devrait aussi fonctionner avec icpc (mais ils ne semblent pas documenter comment ils ont mis en œuvre cela). Mais lorsque je regarde l’ensemble, il semble que la SIC essaie de sortingcher avec float et d’utiliser des outils de virgule flottante de précision plus élevée. Le passage de la -fp-model source au compilateur a corrigé le -fp-model source . Avec cette option, j’obtiens les mêmes résultats qu’avec gcc. Je ne pense pas que vous souhaitiez utiliser ce drapeau en général, ce n’est qu’un test pour vérifier ma théorie.

Donc, pour icpc, je pense que si vous passez de code int / float à long / double, le typage-punning fonctionnera également sur icpc.

Un comportement indéfini ne signifie pas que de mauvaises choses doivent arriver. Cela signifie seulement que la définition du langage ne vous dit pas ce qui se passe. Ce type de jeu de mots fait partie de la programmation en C et C ++ depuis des temps immémoriaux (c.-à-d. Depuis 1969); il faudrait un implémenteur particulièrement pervers pour écrire un compilateur où cela ne fonctionnerait pas.