Motivated from a code snippet on this blog under "What happens when I mix signed and unsigned integers?" I decided to run it with few different values of signed and unsigned integers and observe the behaviour.
Here is the original snippet (slightly modified, however intent is still same)
#include <stdio.h>
int main(void)
{
unsigned int a = 6;
int b = -20;
int c = (a+b > 6);
unsigned int d = a+b;
printf("<%d,%u>", c, d);
}
OUTPUT: <1,4294967282>
Now when I run the same program for a = 6
and b = -1
OUTPUT: <0,5>
If I understand Integral Promotion rule of C language correctly, b=-1
should have been promoted to an unsigned integer, that would have made it 4294967295
. And just like in case of original example we should get <1,4294967301>
. But that is not what we got.
The only reason for this behaviour I could think of was that these implicit type conversions happen after the arithmetic operation. However, the same blog post also says that signed integers are first promoted and then evaluation takes place, which invalidates my reasoning.
What is the real reason for this behaviour? And how the two examples different from each other.
P.S I understand that there are many questions on SO which are similar/related to this problem. But I have not come across any question that addresses this problem particularly or helps in understanding such code snippet. If found to be a duplicate, I would gladly accept this question be closed.
In
b
will get converted tounsigned
because of usual arithmetic conversions (6.3.1.8).The conversion will be done by repeatedly adding or subtracting one more than
UINT_MAX
(6.3.1.3p2) , which forunsigned == uint32_t
means by adding2^32
(one time)For reference where
unsigned == uint32_t
:For
(unsiged)a==6
and(signed)b==-20
, you get:For
(unsiged)a==6
(signed)b==-1
, you get:Now because this result is larger than
UINT_MAX
, it'll wrap-around into5
(which you get by subtractingUINT_MAX+1
, which how the wraparound is defined to happen (6.3.1.3p2)).On two's complement architectures these rules basically translate to a trivial
add
, which means you could just as well do the operation with the signed representations (6-20==-14; 6-1==5
) and then reinterpret the result asunsigned
(seems like a more straightforward way to get the 5 in the 2nd case, doesn't it), but it's still good to know the rules because signed overflow is undefined in C (C!=assembly), which gives your compiler a lot of leeway to transform code into something you'd not expect if you assumed straightforward C to assembly mapping.So you're expected
4294967301
, but that does not fit in a 32-bit unsigned integer, as the largest value is2^32-1
or4294967295
. So4294967295
is equal to0xFFFFFFFF
. With0xFFFFFFFF + 6
is0x00000005
. When a signed integer rolls over from2147483647
(0x7FFFFFFF
) to-2147483648
(0x80000000
) it is called overflow. When an unsigned rolls over from4294967295
(0xFFFFFFFF
) to0
(0x00000000
) it is a wraparound.