cunsigned-integerinteger-promotionsigned-integer

Unreasonable behavior on mixing signed and unsigned integers


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.


Solution

  • In

     /*unsigned*/a+/*signed*/b
    

    b will get converted to unsigned 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 for unsigned == uint32_t means by adding 2^32 (one time)

    For reference where unsigned == uint32_t:

    UINT_MAX+1 == 2^32 == 4294967296
    

    For (unsiged)a==6 and (signed)b==-20, you get:

    2^32-20 + 6 == 4294967282  (<= UINT_MAX)
    

    For (unsiged)a==6 (signed)b==-1, you get:

    2^32-1 + 6 == 4294967301 (>UINT_MAX)
    

    Now because this result is larger than UINT_MAX, it'll wrap-around into 5 (which you get by subtracting UINT_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 as unsigned (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.