bit mobili e aliasing rigoroso

Sto cercando di estrarre i bit da un float senza invocare un comportamento indefinito. Ecco il mio primo tentativo:

unsigned foo(float x) { unsigned* u = (unsigned*)&x; return *u; } 

A quanto ho capito, non è garantito che funzioni a causa delle rigide regole di aliasing, giusto? Funziona se si fa un passo intermedio con un puntatore di caratteri?

 unsigned bar(float x) { char* c = (char*)&x; unsigned* u = (unsigned*)c; return *u; } 

O devo estrarre i singoli byte da solo?

 unsigned baz(float x) { unsigned char* c = (unsigned char*)&x; return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24; } 

Ovviamente questo ha lo svantaggio di dipendere dall’endianità, ma potrei vivere con quello.

Il sindacato hack è decisamente un comportamento indefinito, giusto?

 unsigned uni(float x) { union { float f; unsigned u; }; f = x; return u; } 

Solo per completezza, ecco una versione di riferimento di foo . Anche un comportamento indefinito, giusto?

 unsigned ref(float x) { return (unsigned&)x; } 

Quindi, è ansible estrarre i bit da un float ( supponendo che entrambi siano larghi 32 bit , ovviamente)?


EDIT: Ed ecco la versione memcpy come proposto da Goz. Dal momento che molti compilatori non supportano ancora static_assert , ho sostituito static_assert con alcuni metaprogrammi del modello:

 template  struct requirement; template  struct requirement { typedef T type; }; unsigned bits(float x) { requirement::type u; memcpy(&u, &x, sizeof u); return u; } 

L’unico modo per evitare veramente qualsiasi problema è quello di memcpy.

 unsigned int FloatToInt( float f ) { static_assert( sizeof( float ) == sizeof( unsigned int ), "Sizes must match" ); unsigned int ret; memcpy( &ret, &f, sizeof( float ) ); return ret; } 

Poiché stai memcpying un importo fisso, il compilatore lo ottimizzerà.

Detto questo, il metodo di unione è MOLTO ampiamente supportato.

Il sindacato hack è decisamente un comportamento indefinito, giusto?

Sì e no. Secondo lo standard, è sicuramente un comportamento indefinito. Ma è un trucco così comunemente usato che GCC e MSVC e, per quanto ne so, ogni altro popolare compilatore, garantisce esplicitamente che è sicuro e funzionerà come previsto.

Quanto segue non viola la regola di aliasing, perché non ha alcun uso di lvalue che accedono a tipi diversi ovunque

 template B noalias_cast(A a) { union N { A a; B b; N(A a):a(a) { } }; return N(a).b; } unsigned bar(float x) { return noalias_cast(x); } 

Se vuoi davvero essere agnostico sulla dimensione del tipo float e restituire i bit grezzi, fai qualcosa di simile a questo:

 void float_to_bytes(char *buffer, float f) { union { float x; char b[sizeof(float)]; }; x = f; memcpy(buffer, b, sizeof(float)); } 

Quindi chiamalo così:

 float a = 12345.6789; char buffer[sizeof(float)]; float_to_bytes(buffer, a); 

Questa tecnica produrrà, ovviamente, un output specifico per l’ordinamento dei byte della tua macchina.