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?
CodePudding user response:
*(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 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.)