Integer promotion in C
jake April 22nd, 2008
-
unsigned char t0, t1;
-
unsigned int diff;
-
-
t0 = 0;
-
t1 = 255;
-
-
diff = (unsigned int)(t0 - t1);
Intuitively, I would think that the result of the subtraction would be an unsigned char with value 0x01, which then would get converted to an unsigned int with value 0x00000001 (assuming a 32-bit platform).
Instead, when the program is compiled and run, the output is:
difference: 0xffffff01
Turns out that my compiler is actually doing the right thing here, but C is playing tricks on me.
The C standard has a concept of integer rank. long long has greater rank than long which has greater rank than int, which has greater rank than short, which has greater rank than char. Unsigned types have the same rank as their signed counterparts. Note that even if two integral types have the same size in bits, their rank is different.
For arithmetic operations, the standard specifies that operands of integral data type are promoted to integer rank when their rank is less. Historically, there were two ways of doing so, called unsigned preserving and value preserving. The former simply converts all unsigned chars and shorts into unsigned ints. The latter converts them to signed ints if they are smaller and unsigned ints otherwise (i.e. if short and int are the same size, unsigned shorts will be converted to unsigned ints).
In 1974, Kernighan and Richie's The C Programming Language set the original C standard, and specified the unsigned preserving method. But in 1989 the first ANSI C standard switched (after much debate) to the value preserving method, because it reduces the number of situations in which the result of an expression is questionably signed, meaning it could intuitively be interpreted either way. More detail on this distinction can be found in Section 3.2.1.1 of Rational for the ANSI C Programming Language.
So, in the example above, t0 and t1 are promoted to signed ints before being subtracted. On my machine, ints are 32 bits, so the resulting difference is a signed int of value 0xffffff01 (-255).
This is then converted to unsigned int; the C standard specifies this conversion in an interesting way: "...the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type." In a twos-complement system, this is equivalent to leaving the bits unchanged. In our case, this means that the new value is -255 + 4294967296 = 4294967041 = 0xffffff01.
The correct way to cause the desired wrap of in our example is to insert an explicit cast of the subtracted result back to unsigned char:
diff = (unsigned int)(unsigned char)(t0 - t1);
This produces the intended result:
difference: 0x00000001
A typical 