There's several implicit conversions to keep track of here:
signed char c = 255;
Is a conversion of the constant 255
which has type int
, into a smaller signed char
. This is "lvalue conversion through assignment" (initialization follows the rules of assignment) where the right operand gets converted to the type of the left.
The actual conversion from a large signed type to a small signed type follows this rule:
Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.
In practice, the very likely conversion to happen on a two's complement computer is that you end up with the signed char having the decimal value equivalent to 0xFF
, which is -1
.
c + 1
is an operation with two operands of types signed char
and int
respectively. For the + operator, it means that the usual arithmetic conversions are performed, see Implicit type promotion rules.
Meaning c
gets converted to int
and the operation is carried out on int
type, which is also the type of the result.
printf("%d\n", stuff );
The functions like printf
accepting a variable number of arguments undergo an oddball conversion rule called the default argument promotions. In case of integers, it means that the integer promotions (see link above) are carried out. If you pass c + 1
as parameter, then the type is int
and no promotion takes place. But if you had just passed c
, then it gets implicitly promoted to int
as per these rules. Which is why using %d
together with character type actually works, even though it's the wrong conversion specifier for printing characters.
As per my understanding, it should give negative numbers once it reaches the maximum limit (). Is this correct?
If you simply do signed char c = 127; c++;
then that's a signed overflow, undefined behavior with no predictable outcome.
If you do signed char c = 127; ... c + 1
then there's no overflow because of the implicit promotion to int
.
If you do unsigned char c = 255; c++;
then there is a well-defined wrap around since this is an unsigned type. c
will become zero. Signed types do not have such a well-defined wrap around - they overflow instead.
In practice, signed number overflow is artificial nonsense invented by the C standard. All well-known computers just set an overflow and/or carry bit when you do overflow on assembler level, properly documented and well-defined by the core manual. The reason it turns "undefined behavior" in C is mainly because C allows for nonsensical signedness formats like one's complement or signed magnitude, that may have padding bits, trap representations or other such exotic, mostly fictional stuff.
Though nowadays, optimizing compilers take advantage of overflow not being allowed to happen, in order to generate more efficient code. Which is unfortunate, since we could have had both fast and 100% deterministic code if 2's complement was the only allowed format.