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 specifies that for addition "both operands shall have arithmetic type" (6.5.6.1). Arithemitc types covers integer and floating types (6.2.5.18) and finally integer types are char, short, int, long and long long which exist as signed and unsigned types (6.2.5.4 and 6.2.5.6). According to the rules for usual arithmetic conversion "If both operands have the same type, then no further conversion is needed." So far so good.
It has been my understanding, as exemplified here from "The C Book", that "[n]o arithmetic is done by C at a precision shorter than int" which is where integral promotion is applied. I can find no reference to this in the standard by I seem to have seen this numerous times.
Since unsigned char is an arithmetic type and the rules for usual arithmetic conversion states that operands of the same type does not need conversion, why the need for integral promotion?
I tested this using two different compilers. I wrote a simple program which does char addition:
unsigned char a = 1;
unsigned char b = 2;
unsigned char c = a + b;
The target platform is an Atmel Mega8 uC using an 8 bit architecture. An integer addition would therefore require use of two registers if the operands should be subject to integral promotion.
Compiling this using imagecraft avr compiler with no optimization and with strict and ANSI C portability options enabled yields this assembly code:
mov R16, R20
add R16, R18
Using avr-gcc (I am not aware of an ANSI switch similar to gcc's -strict):
$ avr-gcc -O0 -mmcu=atmega8 -S -c main.c
The resulting assembly:
ldd r25,Y+1
ldd r24,Y+2
add r24,r25
std Y+3,r24
The resulting code in both cases operates on a single byte. I get similar results for bitwise | and & and logical || and &&. Does this mean then that standard allows arithmetic operations on charecter types without integral promotion or does it simply mean that these compilers are not standard complient?
Additional:
Turns out it all depends on the type the result is stored in. The example shown above is only true when the result is stored in a char, and it does not depend on the result of the addition. Setting a to 0xFF and b to 1 produces the exact same assembly code.
If the type of c is changed to unsigned int the resulting assembly looks like this:
mov R2,R20
clr R3
mov R16,R18
clr R17
add R16,R2
adc R17,R3
Even in the case where the result can be held in a single byte, i.e. a=1 and b=2.
See Question&Answers more detail:
os