I know this question has been asked and seemingly answered a gazillion times over but I can\'t seem to match the answers to my own experience.
The C standard specifi
Here's the relevant part from C99:
6.3.1 Arithmetic operands
6.3.1.1 Boolean, characters, and integers
1 Every integer type has an integer conversion rank defined as follows:
...
2 The following may be used in an expression wherever an int or unsigned int may be used:
— An object or expression with an integer type whose integer conversion rank is less than the rank of int and unsigned int.
— A bit-field of type _Bool, int, signed int, or unsigned int.
If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.
I agree it's obscure, but this is the closest you can find w.r.t. conversion of the various kinds of char
or short
or _Bool
to int
or unsigned int
.
From the same source:
5.1.2.3 Program execution
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
...
10 EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the ‘‘integer promotions’’ require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum. Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions.
I think there is no contradiction here. The compiler is not obliged to follow any specific computation path as long as the observable result is as if it would follow the prescribed way.
In particular, for your case, if we would do the computation with promotion to int (say, to 16 bit): a
promoted to int
has the same value, so does b
as well. The value of a + b
is actually (a + b) mod 2^16
, but we assign this to an unsigned char, which is truncating the upper 8 bits, that is the same as taking the result mod 2^8
: ((a + b) mod 2^16) mod 2^8 = (a + b) mod 2^8
.
The calculation without integer promotion would result in (a + b) mod 2^8
, which is exactly the same.
If the computer is capable of performing operations on types smaller than int
, then by all means the standard will never prevent it. Keep in mind that the standard tries to keep as many options available to the compilers and leaves the decision to choose the best method up to them.
The phrase "no arithmetic is done by C at a precision shorter than int" is also correct. If you pay close attention, you will see that the arithmetic is indeed done at a precision that is no shorter than int
. That doesn't mean however that the compiler is forced to do an integer promotion as it can safely perform the operation in your example program on bytes and get the same precision.
In 6.3.1.8 (usual arithmetic conversions, n1570), we can read
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
so integer promotion is part of the usual arithmetic conversions for integer types.
Thus in the abstratc machine, the conversion to (unsigned) int
must be done.
But by the "as if" rule, if the behaviour is indistinguishable from that by strictly implementing the abstract machine, the implementation may do things differently.
So if it is guaranteed that the computation using only single bytes has the same result as the computation promoting to int
, the implementation is allowed to use single-byte arithmetic.
C 2011 (n1570) 6.3.1.8 (“Usual arithmetic conversions”) 1 states that the integer promotions are performed before considering whether the types are the same:
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed…
Thus, in the C abstract machine, unsigned char
values must be promoted to int
before arithmetic is performed. (There is an exception for perverse machines where unsigned char
and int
have the same size. In this case, unsigned char
values are promoted to unsigned int
rather than int
. This is esoteric and need not be considered in normal situations.)
In the actual machine, operations must be performed in a way that gets the same results as if they were performed in the abstract machine. Because only the results matter, the actual intermediate operations do not need to exactly match the abstract machine.
When the sum of two unsigned char
values is assigned to an unsigned char
object, the sum is converted to a unsigned char
. This conversion essentially discards bits beyond the bits that fit in an unsigned char
.
This means that the C implementation gets the same result whether it does this:
int
.int
arithmetic.unsigned char
.or this:
unsigned char
arithmetic.Because the result is the same, the C implementation may use either method.
For comparison, we can consider this statement instead: int c = a + b;
. Also, suppose the compiler does not know the values of a
and b
. In this case, using unsigned char
arithmetic to do the addition could yield a different result than converting the values to int
and using int
arithmetic. E.g., if a
is 250 and b
is 200, then their sum as unsigned char
values is 194 (250 + 200 % 256), but their sum in int
arithmetic is 450. Because there is a difference, the C implementation must use instructions that get the correct sum, 450.
(If the compiler did know the values of a
and b
or could otherwise prove that the sum fit in an unsigned char
, then the compiler could again use unsigned char
arithmetic.)