Take the following code
#include <iostream>
void func() {
int i = 2147483640;
while (i < i + 1)
{
std::cerr << i << '\n';
++i;
}
return;
}
int main() {
func();
}
This code is clearly wrong, as the while loop can only terminate if the signed int i
overflowed, which is UB, and hence the compiler may for instance optimize this into an infinite loop (which Clang does on -O3
), or do other sorts of funky things. My question now is: from my reading of the C++ standard, types that are equivalent up to signedness may alias (i.e. pointers int*
and unsigned*
may alias). In order to do some funky signed "wrapping", does the following have undefined behavior or not?
#include <iostream>
static int safe_inc(int a)
{
++reinterpret_cast<unsigned&>(a);
return a;
}
void func() {
int i = 2147483640;
while (i < safe_inc(i))
{
std::cerr << i << '\n';
++i;
}
return;
}
int main() {
func();
}
I have tried the above code with both Clang 8 and GCC 9 on -O3
with -Wall -Wextra -Wpedantic -O3 -fsanitize=address,undefined
arguments and get no errors or warnings and the loop terminates after wrapping to INT_MIN
.
cppreference.com tells me that
Type aliasing
Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:
- AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
which from my reading means that for purposes of type aliasing, signedness is not considered, and the code using reinterpret_cast
has well-defined semantics (albeit being somewhat cheesy anyhow).
Aliasing here is perfectly legal. See http://eel.is/c++draft/expr.prop#basic.lval-11.2:
If a program attempts to access the stored value of an object through a glvalue whose type is not similar ([conv.qual]) to one of the following types the behavior is undefined:53
(11.1) the dynamic type of the object,
(11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object
I think, it is also worth talking about the actual overflow question, which does not necessarily require reinterpret_cast
. The very same effect could be achieved with implicit integral conversions
unsigned x = i;
++x;
i = x; // this would serve you just fine.
This code would be implementation defined pre-C++20, since you would be converting from the value which can't be represented by destination type.
Since C++20 this code will be well-formed.
See https://en.cppreference.com/w/cpp/language/implicit_conversion
On a side note, you might as well start with unsigned type if you want integer overflow semantic.
Your code is perfectly legal, cpp reference is a very good source. You can find the same information in the standard [basic.lval]/11
If a program attempts to access the stored value of an object through a glvalue whose type is not similar ([conv.qual]) to one of the following types the behavior is undefined:
the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,[...]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With