c++ - reinterpretar - interpretar la realidad significado
La forma más eficiente y eficiente de reinterpretar int como float (4)
Afaik, solo hay dos enfoques que cumplen con las reglas estrictas de aliasing: memcpy()
y cast to char*
con copia. Todos los demás leen un float
de la memoria que pertenece a uint32_t
, y el compilador puede leer antes de escribir en esa ubicación de memoria. Incluso podría optimizar la escritura por completo, ya que puede demostrar que el valor almacenado nunca se utilizará de acuerdo con reglas de aliasing estrictas, lo que resulta en un valor de retorno de basura.
Realmente depende del compilador / optimiza si la memcpy()
o char*
es más rápida. En ambos casos, un compilador inteligente podría darse cuenta de que solo puede cargar y copiar un uint32_t
, pero no confiaría en ningún compilador antes de haberlo visto en el código ensamblador resultante.
Editar:
Después de algunas pruebas con gcc 4.8.1, puedo decir que el enfoque de memcpy()
es el mejor para este compilador de particulare, ver más abajo para más detalles.
Compilando
#include <stdint.h>
float foo(uint32_t a) {
float b;
char* aPointer = (char*)&a, *bPointer = (char*)&b;
for( int i = sizeof(a); i--; ) bPointer[i] = aPointer[i];
return b;
}
con gcc -S -std=gnu11 -O3 foo.c
produce este código de ensamblaje:
movl %edi, %ecx
movl %edi, %edx
movl %edi, %eax
shrl $24, %ecx
shrl $16, %edx
shrw $8, %ax
movb %cl, -1(%rsp)
movb %dl, -2(%rsp)
movb %al, -3(%rsp)
movb %dil, -4(%rsp)
movss -4(%rsp), %xmm0
ret
Esto no es óptimo
Haciendo lo mismo con
#include <stdint.h>
#include <string.h>
float foo(uint32_t a) {
float b;
char* aPointer = (char*)&a, *bPointer = (char*)&b;
memcpy(bPointer, aPointer, sizeof(a));
return b;
}
rendimientos (con todos los niveles de optimización excepto -O0
):
movl %edi, -4(%rsp)
movss -4(%rsp), %xmm0
ret
Esto es óptimo.
Supongo que tengo garantías de que float
es IEEE 754 binary32. Dado un patrón de bits que corresponde a un float válido, almacenado en std::uint32_t
, ¿cómo se lo reinterpreta como un float
de la manera más eficiente y compatible?
float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}
Tengo algunas maneras que sé / sospecho / supongo que tienen algunos problemas:
A través de
reinterpret_cast
,float reinterpret_as_float(std::uint32_t ui) { return reinterpret_cast<float&>(ui); }
o equivalente
float reinterpret_as_float(std::uint32_t ui) { return *reinterpret_cast<float*>(&ui); }
que sufre problemas de aliasing.
A través de la
union
,float reinterpret_as_float(std::uint32_t ui) { union { std::uint32_t ui; float f; } u = {ui}; return u.f; }
que en realidad no es legal, ya que solo está permitido leer desde el más reciente escrito para el miembro. Sin embargo, parece que algunos compiladores (gcc) lo permiten.
Via
std::memcpy
,float reinterpret_as_float(std::uint32_t ui) { float f; std::memcpy(&f, &ui, 4); return f; }
que AFAIK es legal, pero una llamada a función para copiar una sola palabra parece un desperdicio, aunque podría optimizarse.
Via
reinterpret_cast
achar*
y copiado,float reinterpret_as_float(std::uint32_t ui) { char* uip = reinterpret_cast<char*>(&ui); float f; char* fp = reinterpret_cast<char*>(&f); for (int i = 0; i < 4; ++i) { fp[i] = uip[i]; } return f; }
que AFAIK también es legal, ya que los punteros de
char
están exentos de problemas de aliasing y el bucle de copia de byte manual guarda una posible llamada de función. El loop definitivamente se desenrollará, sin embargo, 4 cargas / tiendas de un byte posiblemente son preocupantes, no tengo idea si esto es optimizable para una carga / almacenamiento de solo cuatro bytes.
El 4
es lo mejor que he podido inventar.
¿Estoy correcto hasta ahora? ¿Hay una mejor manera de hacerlo, en particular una que garantice una sola carga / tienda?
Si el patrón de bits en la variable entera es lo mismo que un valor float
válido, entonces la unión es probablemente la mejor y más flexible forma de proceder. Y en realidad es legal si lee la especificación (no recuerdo la sección en este momento).
memcpy siempre es seguro pero implica una copia
fundición puede conducir a problemas
unión - parece estar permitido en C99 y C11, no estoy seguro acerca de C ++
Echa un vistazo a:
¿Cuál es la regla de aliasing estricta?
y
float reinterpret_as_float(std::uint32_t ui) {
return *((float *)&ui);
}
Como función simple, su código se traduce en ensamblaje como este (Pelles C para Windows):
fld [esp+4]
ret
Si se define como función en inline
, entonces un código como este ( n
es unsigned, x
es flotante):
x = reinterpret_as_float (n);
Se traduce al ensamblador como esto:
fld [ebp-4] ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8] ;LHS of asignment