The rules, known as the usual arithmetic conversions seem complicated. The C99 standard says:
If both operands have the same type, then no further conversion is needed.
Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
The most worrying part of this specification is point 3 as converting a signed integer to an unsigned of the same rank seems to invalidate the arithmetic. You have to remember that two’s-complement arithmetic works even if you consider the value to be unsigned. For example:
unsigned int myUnsigned=1;
In this case the signed int is converted to unsigned and it looks as though we are going to get the answer 2 as the negative is lost. This is not how it works. The signed int is converted to an unsigned int with the same bit pattern. When added together the roll over occurs and the result is the same in two's-complement. That is, it prints 0 and we have the correct answer.
This doesn’t mean that you cannot get the wrong answer. For example:
unsigned int myUnsigned=UINT_MAX;
In this case you will still see zero printed, which in this case is probably the wrong answer. Again we have the same bit patterns. The UINT_MAX is -1 when regarded as a signed int and hence the final result is zero. To get the correct answer we need to explicitly use a cast to long long:
unsigned int myUnsigned=UINT_MAX;
long long mylong= (long long)myInt+(long long)myUnsigned;
In fact, we don’t need to cast myInt to long long because the rules will promote it to the same as myUnsigned.
Notice that these casts are only performed in expressions, including comparison operators, and bitwise operators. They are not performed for assignment operators. In most cases the result of expressions involving mixed types give you the correct answer but if in doubt use explicit casts.
A common idiom, however, is to rely on the rules to change integer arithmetic into floating-point arithmetic. For example:
gives the answer 0 as integer division is performed before the result is converted to a float. However:
gives the result 0.01 as the literal is now a float and hence myInt is automatically converted to a float.
In chapter but not in this extract
Postfix and Prefix Increment
Conditional and Comma
Sequence Points and Lazy Evaluation
Expressions form a sophisticated programming language in their own right consisting of operators with precedence and associativity rules.
Casts allow bit patterns to represent different things.
Widening casts always preserve the original value.
Narrowing casts only preserve the original value if it is representable in the smaller type.
The rules for mixed type expressions are complicated - if in doubt use an explicit cast.
Sequence points determine when the side effects of an expression are determined and sequence point rules make some expressions undefined.
Logical OR and AND are lazy evaluated which means that the right-hand expression may never be evaluated and hence may never cause any side effects.
If you find you are having to work out sequence points and rules the chances are you are writing obscure code.