C warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

Trick C question time … what will this print?

#include <stdio.h>
#include <stdlib.h>

int main()
{
  int x;
  unsigned int y;

  x = -1;
  y = 2;

  printf("x = %d\n", x);
  printf("y = %u\n", y);

  if ( x > y )
  {
    printf("x > y\n");
  }
  else if (x < y)
  {
    printf("x < y\n");
  }
  else
  {
    printf("x == y\n");
  }

  return EXIT_SUCCESS;
}

I recently began looking in to various compiler warnings in some code I am using, and I found quite a few warnings about comparing signed and unsigned values:

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

I thought I could safely ignore these, since it seems plausible to compare a signed value with an unsigned value. A signed value of -42 should be less than an unsigned value of 42, right?

In the above example, it will print the following:

x = -1
y = 2
x > y

Nope. I was wrong. According to C, -1 is greater than 2.

C does something that I either never knew, or knew and have long since forgotten. I guess I generally try to write code that has no warnings at all, so I’ve avoided doing this. And now I know (or re-know) the reason why.

When dealing with mis-matched comparisons, C makes them both unsigned. Thus, “-1” becomes whatever -1 would be for that data type.

char  achar  = -1;
short ashort = -1;
int   aint   = -1;
long  along  = -1;

printf("char  -1 as unsigned: %u\n", (unsigned char)achar);
printf("short -1 as unsigned: %u\n", (unsigned short)ashort);
printf("int   -1 as unsigned: %u\n", (unsigned int)aint);
printf("long  -1 as unsigned: %u\n", (unsigned long)along);

This outputs:

char  -1 as unsigned: 255
short -1 as unsigned: 65535
int   -1 as unsigned: 4294967295
long  -1 as unsigned: 4294967295

Thus, on a PC, an 8-bit signed value of -1 is treated as a 255 when comparing against an unsigned value, and a 16-bit as 65535. It seems an int and long as both 32-bits on my system, but these could all be different on other architectures (on Arduino, and int is 16-bits, I believe).

So, without this warning enabled, any comparison that looks correct might be doing something quite wrong.

Warnings are our friends. Even if we hate them and want them to go away.

 

2 thoughts on “C warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

  1. William Astle

    On most architectures, you’ll get the same results. That’s because most architectures are using two’s complement representation of integers. If you do a naïve cast from signed to unsigned (that is, don’t check for negative first and just treat the bit pattern as a positive number), all negative numbers will be greater than all positive numbers. Note that the same thing should happen even on a 6809.

    As you discovered, there’s a really good reason for the signed/unsigned warnings.

    The problem here is actually in the C language itself, though. Comparing signed and unsigned *should* be safe and it can be implemented. However, the code for that gets messy and in many cases isn’t actually needed (say if the programmer knows the domain of the signed value will never be negative).

    Instead, C went the way that made things easier for the compiler. I think originally casting a negative to unsigned was just “undefined” which means that compiler can technically do anything including emit code to format your hard drive. Most compiler writers just went for the easy thing – treat the bits of the signed value as unsigned. And, poof! You get the behaviour you discovered. Newer C specifications may have made that behaviour more explicit.

    Actually, there is a *lot* of stuff in C that is undefined but which any reasonable programmer would expect to behave sensibly. That’s partly why older code generates so many warnings on modern compilers. Compilers have been getting a lot better at warning about undefined behaviours.

    Reply
  2. James Jones

    Things to look for: the “integer promotions” and “usual arithmetic conversions”. At least ANSI C specifies what goes on; before that, you had a tossup between unsigned-preserving and value-preserving widening.

    Reply

Leave a Reply