C++ uniones vs. reinterpretación
unions reinterpret-cast (3)
El comportamiento indefinido no significa que deben suceder cosas malas. Solo significa que la definición de idioma no le dice lo que sucede. Este tipo de juego de palabras ha sido parte de la programación en C y C ++ desde tiempos inmemoriales (es decir, desde 1969); Se necesitaría un implementador particularmente perverso para escribir un compilador donde esto no funcionó.
En otras preguntas de StackOverflow y en la sección §9.5.1 del borrador de la norma ISO / IEC C ++ estándar se desprende que el uso de uniones para hacer una reinterpret_cast
literal de datos es un comportamiento indefinido.
Considere el siguiente código. El objetivo es tomar el valor entero de 0xffff
e interpretarlo literalmente como una serie de bits en el punto flotante IEEE 754. ( Convertir en binario muestra visualmente cómo se hace esto ) .
#include <iostream>
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<float*>(&i);
cout << "theFloat " << theFloat << endl;
return 0;
}
Se espera la salida de este código, que usa tanto compiladores GCC como clang.
size of int 4
size of float 4
myInt 65535
myFloat 9.18341e-41
theFloat 9.18341e-41
Mi pregunta es, ¿el estándar realmente impide que el valor de myFloat
sea determinista? ¿El uso de un reinterpret_cast
mejor de alguna manera para realizar este tipo de conversión?
La norma establece lo siguiente en §9.5.1:
En una unión, como máximo, uno de los miembros de datos no estáticos puede estar activo en cualquier momento, es decir, el valor de a lo sumo uno de los miembros de datos no estáticos se puede almacenar en una unión en cualquier momento. [...] El tamaño de una unión es suficiente para contener el mayor de sus miembros de datos no estáticos. Cada miembro de datos no estáticos se asigna como si fuera el único miembro de una estructura. Todos los miembros de datos no estáticos de un objeto de unión tienen la misma dirección.
La última oración, que garantiza que todos los miembros no estáticos tienen la misma dirección, parece indicar que se garantiza que el uso de una unión es idéntico al uso de un reinterpret_cast
, pero la declaración anterior sobre los miembros de datos activos parece excluir esta garantía.
Entonces, ¿qué construcción es más correcta?
Edición: Usando el compilador icpc
de Intel, el código anterior produce resultados aún más interesantes:
$ icpc union.cpp
$ ./a.out
size of int 4
size of float 4
myInt 65535
myFloat 0
theFloat 0
La razón por la que no está definido es porque no hay garantía de cuáles son exactamente las representaciones de valor de int
y float
. El estándar de C ++ no dice que un float
se almacene como un número de punto flotante de precisión simple IEEE 754. ¿Qué debería decir exactamente el estándar acerca de que usted trata un objeto int
con valor 0xffff
como un float
? No dice nada más que el hecho de que está indefinido.
Sin embargo, en la práctica, este es el propósito de reinterpret_cast
: decirle al compilador que ignore todo lo que sabe sobre los tipos de objetos y que confíe en usted que este int
es en realidad un float
. Casi siempre se usa para piquetas de cocina de nivel de bits específicas de la máquina. El estándar C ++ no te garantiza nada una vez que lo haces. En ese momento, depende de usted entender exactamente lo que hacen su compilador y su máquina en esta situación.
Esto es cierto tanto para los enfoques union
como reinterpret_cast
. Sugiero que reinterpret_cast
es "mejor" para esta tarea, ya que aclara la intención. Sin embargo, mantener su código bien definido es siempre el mejor enfoque.
No es un comportamiento indefinido. Es un comportamiento de implementación definido. Lo primero significa que pueden pasar cosas malas. El otro significa que lo que sucederá tiene que ser definido por la implementación.
El reinterpret_cast viola la regla estricta de aliasing. Así que no creo que funcione de forma fiable. El truco sindical es lo que la gente llama type-punning
y generalmente los compiladores lo permiten. La gente de gcc documenta el comportamiento del compilador: http://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit_002dfields-implementation.html#Structures-unions-enumerations-and-bit_002dfields-implementation
Creo que esto también debería funcionar con icpc (pero no parecen documentar cómo implementaron eso). Pero cuando miré el ensamblaje, parece que icc intenta hacer trampa con flotador y usar cosas de punto flotante de mayor precisión. Al -fp-model source
al compilador se corrigió eso. Con esa opción, obtengo los mismos resultados que con gcc. No creo que quieras usar esta bandera en general, esto es solo una prueba para verificar mi teoría.
Así que para icpc, creo que si cambias tu código de int / float a long / double, el tip-tip también funcionará en icpc.