4

Consider this C code:

#include "stdio.h"

int main(void) {

    int count = 5;
    unsigned int i;

    for (i = count; i > -1; i--) {
        printf("%d\n", i);
    }
    return 0;
}

My observation/question: the loop never gets executed. But if I change the data type of i from unsigned int to int, everything works as expected.

I've been thinking of unsigned ints as values that "wrap around" when you try to keep subtracting from them. So, when i is zero and I subtract 1, it would wrap around to UINT_MAX. And since its value is never negative, this would be effectively an endless loop. (And this is exactly what happens when I change the comparison from i > -1 to i >= 0.)

There is a fault somewhere in my logic, as the loop never gets executed if i is unsigned, and I'm comparing it to -1. Either the compiler optimizes it away somehow or the runtime values behave differently from what I expect.

Why does the loop not get run?

Jaanus
  • 17,688
  • 15
  • 65
  • 110
  • Have you tried an explicit cast of the literal value -1? – 0xC0000022L Jun 02 '11 at 17:17
  • You are completely right. But keep in mind that 5 > UINT_MAX is false. – sidyll Jun 02 '11 at 17:18
  • http://stackoverflow.com/questions/2084949/arithmetic-operations-on-unsigned-and-signed-integers http://stackoverflow.com/questions/24466857/why-sizeofint-is-not-greater-than-1 http://stackoverflow.com/questions/3100365/why-is-%E2%88%921-sizeofint – phuclv Mar 24 '15 at 11:59

4 Answers4

20

In i > -1, the -1 is converted to unsigned int, resulting in the value UINT_MAX. i is never bigger than that value, so the loop body never executes.

You might find that you can persuade your compiler to warn you about this: use of an always-true or always-false expression in a conditional context. But that still wouldn't help you if you'd written i > -2, so you may also find you can enable a warning for all mixed-sign comparisons.

Note that in C, arithmetic is always performed with operands of equal type. This includes comparisons but IIRC not the shift operators. If the operands are of different type, as in this case, then at least one of them is converted to make them the same type. The rules for working out the destination type are in 6.3.1.1/2 and 6.3.1.8/1.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
3

When you mix signed and unsigned operands of the same width in a "type-symmetrical" binary operation (like +, * or > in your example), the unsigned type "wins" and the operation is evaluated in unsigned domain. I.e. signed operand gets converted to unsigned type.

In your example the integer constant has type signed int, while i has type unsigned int. Operands have the same width, so in your example i > -1 is interpreted as i > (unsigned) -1, which is equivalent to i > UINT_MAX. This is why your loop is never executed.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • In general this is not true that unsigned type wins. Try, for example, `unsigned char` and `int`. – vitaut Jun 02 '11 at 17:23
  • @vitaut: Well, yes, you are right, but in reality this is not a very good example. When you use `unsigned char` in an expression, the usual arithmetic conversions apply first and `unsigned char` becomes `signed int` very early. It happens before the "binary" rule I'm talking about in my answer has a chance to kick in. So, figuratively speaking, you simply "can't" use `unsigned char` in an expression - it won't stick. It will snap to `int` before anything else gets a chance to happen. – AnT stands with Russia Jun 02 '11 at 17:31
  • for a better example, `unsigned int` loses to `signed long long`, assuming (as is common) that `long long` can represent all the values of `unsigned int`. So `(unsigned int)5 > -1LL` is true in most implementations, but false if `int` and `long long` have the same width. This is a *really good* reason not to do mixed comparisons between `unsigned int` and `long`: you'll get a signed comparison on LP64 machines and an unsigned comparison on LLP64 and (probably) 32 bit. – Steve Jessop Jun 02 '11 at 17:46
  • @Steve Jessop: Good point. I updated my answer to make it refer to types of the same width. – AnT stands with Russia Jun 02 '11 at 18:11
  • @SteveJessop: I wonder why the implementers of the C standard don't specify that in situations where the 'normal' rules would cause a negative number to compare larger than a positive number, the result shall be implementation defined? Should any code that relies upon the C behavior not be considered horribly deprecated? – supercat May 14 '12 at 00:21
1

Whether you are dealing with unsigned or signed numbers, -1 will always be compiled as 0xffffffff. The processor has both signed and unsigned comparison flags. When comparing that number to 5, the signed flags will treat it as -1 and say it is less, but the unsigned flags will treat it as a large number and say it is greater. Since that number is also the same as UINT_MAX, your comparison will be false for all unsigned numbers.

ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
  • "always be compiled as 0xffffffff" (4,294,967,295) - Thats not true, the standard does not make any guarantee to the value of `UINT_MAX`. In fact, all it states is that "Their implementation-defined values shall be equal or greater in magnitude (absolute value) to 65535 (2^16-1)" – Wiz Jun 02 '11 at 17:44
  • This is the only answer that correctly points out that the -1 is unchanged. It is just being treated as an unsigned value when used in an unsigned comparison. – stark Sep 13 '22 at 13:36
0

-1 becomes UINT_MAX in unsigned comparisons. As no number is greater than that, the loop condition is never true and the loop is never entered.

If you change it to i >= 0, that should work as expected. Actually you probably just shouldn't be using unsigned in this case :-)

Platinum Azure
  • 45,269
  • 12
  • 110
  • 134