Normal "int" is not dynamic, but is not common across compilers; it's the size that is most convenient for the CPU you're compiling for. You would want to designate a size if you have some reason (network protocol, file format, etc) why you actually care about the representation.
Answers to your exercises below the answer to your question:
Your answer can be found in the C11 final draft spec (was ratified in October 2011, but the "final" document is not free): http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
The first relevant section is 6.3.1.1 Conversions : Arithmetic Operands : Boolean, characters and integers, beginning on page 50
If an int
can represent all values of the original type (as restricted by the width, for a bit-field), 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.
The integer promotions preserve value including sign. As discussed earlier, whether a "plain" char
is treated as signed is implementation-defined.
Essentially, the integer types are all promoted to at least int, and possibly to larger sizes than that if any of the operands had a rank greater than that. They generally become signed, but remain unsigned if either operand's type has greater range than int.
There's a bunch of stuff specifically defining how the conversions are performed, and codifying the rules of which types are "higher rank" (bigger) than others, and then we reach the real meat in 6.3.1.8: Usual arithmetic conversions:
Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the
usual arithmetic conversions:
... some stuff about floats ...
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. 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.
int8_t even = 0xBB;
0xBB formed as a constant of type int
(0x000000BB), then truncated down to int8_t
int16_t twice = even << 8;
the value of even is sign extended from int8_t
to int
, becoming 0xFFFFFFBB (if I assume int
is 32-bits on this machine)
The value of 8 is formed as type int
These types match, so no further conversions occur
The operator << is performed (now that types match), yielding 0xFFFFBB00
This value is truncated to fit in int16_t
, yielding 0xBB00
twice = twice + even;
The value of twice is sign extended from int16_t
to int
, yielding 0xFFFFBB00 (-17664)
The value of even is sign extended from int8_t
to int
, yielding 0xFFFFFFBB (-69)
These values are added, yielding 0xFFFFBABB (-17733)
This value is then truncated to fit in int16_t
, yielding 0xBABB
What happens when I add to 8 bit ints that are both 0xFF?
They are both sign extended to int
yielding 0xFFFFFFFF, then added yielding 0xFFFFFFFE. This may or may not be truncated back down depending on where you store it.
The compiler is of course free to notice that much of this extending/truncating stuff is worthless busywork because the output bits are not kept, and optimize it out. But the result is required to be "as if" if had done things the long way.