If the operation is addition, i.e. x + y = z
, assuming x = -2147483647
(signed integer), and y = -1U
, then -2147483647 + (-1U) = z
What is -1U
? Is it signed? Unsigned?
Overflow vs wrap-around, undefined behavior vs well-defined behavior
Wrap-around, going from 0 to something like UINT_MAX
or the other way around, is only well-defined for unsigned numbers.
If you attempt something like that with signed numbers, we don't get wrap-around but overflow and the behavior is not well-defined in the C language. The program might as well crash or misbehave as to produce a wrap-around effect.
This is kind of an historical defect in the C language, because it allows (for now) many forms of signed formats and not just two's complement, which is by far the most common form. And overflows in the various signed formats are not consistent with each other.
(Imagine a signed byte with well-defined overflow. 2's complement 127 would realistically overflow into -128, but signed magnitude 127 would overflow into 0 because there's no reason why overflow would affect the sign bit. Similarly, signed magnitude -128 would underflow into -0 whereas 2's complement -128 would underflow into 127.)
Whereas on the assembler level, overflows of 2's complement arithmetic are always well-defined. Most instruction sets (ISA) sets an overflow bit when that happens but the result is otherwise deterministic, not like in C where anything can happen.
Integer constants, their types and implicit promotion
We may note that every integer constant such as 2147483647
has a type in C just like a variable. The default type is int
but if we append an U
or u
suffix, the type turns unsigned.
Integer constants can never be negative in C. The -
is actually the unary minus operator applied to a positive value.
The C code -2147483647
or -(2147483647)
both give the value -2147483647. But The C code -1U
or -(1U)
cannot result in a negative value because the type of the integer constant is unsigned int
. So assuming 32 bit int
, we get a wrap-around effect instead, resulting in the positive value 4294967295.
Therefore -2147483647 + (-1U)
equals -2147483647 + 4294967295U
. However, in this expression the left integer constant is of type int
and the right one of type unsigned int
. One of the Implicit type promotion rules known as "the usual arithmetic conversions" applies, stating the the signed operand gets converted to unsigned type. This conversion is well-defined and we end up with the equivalent of 2147483649U + 4294967295U
.
2147483649U + 4294967295U
leads to wrap-around and the result is 2147483648U
, of unsigned int
type.
Converting back to signed form
If we would convert this back to int
which is supposedly the type of "z" in your example, 2147483648 will not fit inside a signed int
. The result of the conversion is then implementation-defined = compiler-specific.
The most reasonable and common behavior implemented by most compilers, is then to turn this into the signed representation equivalent, which is -2147483648
. Or in raw binary 0x80000000, which is the raw binary representation of 2147483648 as well, if it had fit.
So -2147483647 + -1U
converted back to signed int
is likely to give the same result as -2147483647 + -1
, namely -2147483648.