I have the following sample code:
inline float successor(float f, bool const check)
{
const unsigned long int mask = 0x7f800000U;
unsigned long int i = *(unsigned long int*)&f;
if (check)
{
if ((i & mask) == mask)
return f;
}
i++;
return *(float*)&i;
}
float next1(float a)
{
return successor(a, true);
}
float next2(float a)
{
return successor(a, false);
}
Under x86-64 clang 13.0.1
, the code compiles as expected.
Under x86-64 clang 14.0.0
or 15, the output is merely a ret
op for next1(float)
and next2(float)
.
Compiler options: -march=x86-64-v3 -O3
The code and output are here: Godbolt.
The successor(float,bool)
function is not a no-op.
As a note, the output is as expected under GCC, ICC, and MSVCC. Am I missing something here?
*(unsigned long int*)&f
is an immediate aliasing violation. f
is a float
. You are not allowed to access it through a pointer to unsigned long int
. (And the same applies to *(float*)&i
.)
So the code has undefined behavior and Clang likes to assume that code with undefined behavior is unreachable.
Compile with -fno-strict-aliasing
to force Clang to not consider aliasing violations as undefined behavior that cannot happen (although that is probably not sufficient here, see below) or better do not rely on undefined behavior. Instead use either std::bit_cast
(since C++20) or std::memcpy
to create a copy of f
with the new type but same object representation. That way your program will be valid standard C++ and not rely on the -fno-strict-aliasing
compiler extension.
(And if you use std::memcpy
add a static_assert
to verify that unsigned long int
and float
have the same size. That is not true on all platforms and also not on all common platforms. std::bit_cast
has the test built-in.)
As noticed by @CarstenS in the other answer, given that you are (at least on compiler explorer) compiling for the SysV ABI, unsigned long int
(64bit) is indeed a different size than float
(32bit). Consequently there is much more direct UB in that you are accessing memory out-of-bounds in the initialization of i
. And as he also noticed Clang does seem to compile the code as intended when an integer type of matching size is used, even without -fno-strict-aliasing
. This does not invalidate what I wrote above in general though.