2

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.

user3276435
  • 265
  • 1
  • 3
  • 13
  • i don't understand what the problem is. This all seems like reasonable behavior to me. Is it that `c` should be `0` in the 6, -20 case? – TinyTheBrontosaurus Dec 02 '17 at 21:19
  • 2
    The first output is basically "is 4294..... above 6, then 1, and then the number 4294....". The second output is "Is 5 above 6, then 1, otherwise 0, and then the number 5". **what is your question here?** – Lasse V. Karlsen Dec 02 '17 at 21:19
  • 1
    Let me summarize. Yes, 4294967282 is > 6, so then 1. No, 5 is not > 6, so then 0. **What is your question?** – Lasse V. Karlsen Dec 02 '17 at 21:20
  • Please bear in mind that if you're adding two numbers, a+b, where a is unsigned and b is signed, then "it will work out correctly" if you simply reinterpret the signed value as an unsigned value in terms of what the bits mean. – Lasse V. Karlsen Dec 02 '17 at 21:22
  • The `? 1 : 0` is unnecessary. The `>` operator yields a result of type `int`, `1` if the condition is true, `0` if it's false. – Keith Thompson Dec 02 '17 at 21:30
  • @KeithThompson Thank you for pointing that out. I think that was a direct consequence of overly emphasizing readability of code. Anyways, I have fixed it. – user3276435 Dec 03 '17 at 00:13

2 Answers2

2

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.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 'Wrap-around' was the key through this. I guess the monstrous number 4294967301 made it difficult for me to see that it went out of range. Thank you for the last paragraph which made sense out of my sceptical reasoning I gave in question. – user3276435 Dec 03 '17 at 00:09
  • BTW is there a quick example that you can share in which we 'subtract' one more than UINT_MAX to do the type conversion? I mean when do we really subtract for type conversion? – user3276435 Dec 03 '17 at 00:20
  • @Prateek E.g., The last snippet, where 4294967301 gets converted to 5. – Petr Skocik Dec 03 '17 at 00:38
  • But that's just a wrap around right? An unsigned addition that went out of range and was wrapped. I don't think there is any conversion of data type happening over there. – user3276435 Dec 03 '17 at 01:18
  • 1
    @Prateek It works the same. `uint64_t a = 4294967301UL; uint32_t b = a;` Here you've got a real 64 bit 4294967301 getting connverted to `(uint32_t)5` by subtracting `UINT32_MAX + 1`. – Petr Skocik Dec 03 '17 at 08:07
0

So you're expected 4294967301, but that does not fit in a 32-bit unsigned integer, as the largest value is 2^32-1 or 4294967295. So 4294967295 is equal to 0xFFFFFFFF. With 0xFFFFFFFF + 6 is 0x00000005. When a signed integer rolls over from 2147483647 (0x7FFFFFFF) to -2147483648 (0x80000000) it is called overflow. When an unsigned rolls over from 4294967295 (0xFFFFFFFF) to 0 (0x00000000) it is a wraparound.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
TinyTheBrontosaurus
  • 4,010
  • 6
  • 21
  • 34