Search code examples
ctypesliterals

Why is an int variable valued `0xffffffff >> 1` != 0x7fffffff, unlike for a literal constant?


  int c = 0xffffffff;
  printf("%x\n", (c) == (0xffffffff));
  printf("%x\n", (c >> 1) == (0xffffffff >> 1));

The first output is 1 but the second output is 0. And c >> 1 outputs 0xffffffff. But why?


Solution

  • Because 0xffffffff on your platform is a value of type unsigned int. Therefore 0xffffffff >> 1 equals 0xffffffffU >> 1 which is 0x7fffffffU.

    When you assign a value of type unsigned int to a variable of type int the value is converted to the target type. 0xffffffff i.e. 4294967295 is outside the range of a 32-bit signed integer, so naturally it cannot be stored as is. But because your computer uses two's complement system, the bits will remain the same, i.e. all bits will be set, but the value is interpreted as -1 instead of 4294967295.

    The behaviour of right shift on negative integers is implementation-defined in the C standard. GCC defines it as arithmetic right shift, i.e. using sign extension. So the bit representation ffffffff shifted right by 1 will result in ffffffff bit representation; which as signed int signifies value -1.

    Finally when you compare an int against an unsigned int using the == operator, the operands will undergo "usual arithmetic conversions" - the int -1 will be converted to an unsigned int - which on two's complement systems retains the bit pattern i.e. ffffffff, but now the value will be interpreted as 4294967295 instead of -1. But that value compares unequal to 0x7fffffff (2147483647), which has value


    What is more confusing is that the value of c does not equal the 0xffffffff even though the equality operator returns true! The value of c will be -1, and the value of 0xffffffff is 0xffffffffu i.e. 4294967295 and clearly the difference between -1 and 4294967295 is 4294967296! But it is exactly because of the signed/unsigned comparison that different values can compare to equal - and for example GCC will warn about this with -Wextra:

    warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
        5 |   printf("%x\n", (c) == (0xffffffff));
          |                      ^~
    [venv][vrcappsdev][master✗]                                                                                                                                                                      
    

    To see that the values indeed are distinct, you need to cast both sides to a type that is large enough to contain both of them - long long int is a portable one:

    printf("%d\n", (long long int)c == (long long int)0xffffffff);
    

    will not print 1.