This page is an extension of this post on aliasing. In particular, that thread includes several suggestions of code that is undefined according to the standards, which can cause real errors (at least in GCC).
The following example includes various ways of converting a float's bytes into an int:
#include <cstdio>
#include <cstring>
inline int float_to_int(float f) {
#if NUM == 1
return *(int*)&f;
#elif NUM == 2
return *(int*)(char*)&f;
#elif NUM == 3
return *(int*)(void*)&f;
#elif NUM == 4
return *reinterpret_cast<int*>(&f);
#elif NUM == 5
union { float f; int i; } u;
u.f = f;
return u.i;
#elif NUM == 6
int i;
((char*)&i)[0] = ((char*)&f)[0];
((char*)&i)[1] = ((char*)&f)[1];
((char*)&i)[2] = ((char*)&f)[2];
((char*)&i)[3] = ((char*)&f)[3];
return i;
#elif NUM == 7
int i;
memcpy(&i, &f, 4);
return i;
#endif
}
int main() {
float f = 1.0;
printf("%d: %08x\n\n", NUM, float_to_int(f));
}
(This assumes sizeof(int) == sizeof(float) == 4
. Only one of the sections of code is compiled each time, to prevent interference that obscures the results.)
The printed output should be 3f800000
in every case. Compiling with g++ -O1
, that is indeed the output.
But g++ -O2
gives very different results:
$ for n in 1 2 3 4 5 6 7; do ( g++-4.3.1 -O2 -Wall -DNUM=$n float-aliasing.cpp && ./a.out ); done float-aliasing.cpp: In function ‘int float_to_int(float)’: float-aliasing.cpp:6: warning: dereferencing type-punned pointer will break strict-aliasing rules 1: b7fa8050 float-aliasing.cpp: In function ‘int float_to_int(float)’: float-aliasing.cpp:8: warning: dereferencing type-punned pointer will break strict-aliasing rules 2: b7f3c050 float-aliasing.cpp: In function ‘int main()’: float-aliasing.cpp:10: warning: likely type-punning may break strict-aliasing rules: object ‘*{unknown}’ of main type ‘int’ is referenced at or around float-aliasing.cpp:10 and may be aliased to object ‘f’ of main type ‘float’ which is referenced at or around float-aliasing.cpp:33. 3: b7f9f050 float-aliasing.cpp: In function ‘int float_to_int(float)’: float-aliasing.cpp:12: warning: dereferencing type-punned pointer will break strict-aliasing rules 4: b7f30050 5: 3f800000 6: 3f800000 7: 3f800000
The first four cases all produce entirely bogus output (which is permitted because the code is relying on undefined behaviour).
GCC 4.2 doesn't warn about the third case – that warning is new in 4.3 – but it still produces bogus output.
In the final case, the memcpy
function call is optimised away entirely.
Even if the float is not a compile-time constant, the compiler just generates code to store
the value into memory (fstps
) then loads it back into an integer register (movl
).
So if you want a float_to_int
that has safe, portable, well-defined behaviour
(at least to the extent that the integer representation of floats is portable and well-defined),
and you don't want to worry about eager compilers breaking your code, use memcpy
.