To understand behavior of your code, you need to learn the concept called 'Integer Promotions' (that happens in your code implicitly before bit wise NOT operation on an unsigned char
operand) As mentioned in N1570 committee draft:
- The result of the
~
operator is the bitwise complement of its
(promoted) operand (that is, each bit in the result is set if and only
if the corresponding bit in the converted operand is not set). The
integer promotions are performed on the operand, and the result has
the promoted type. If the promoted type is an " 'unsigned type', the
expression ~E
is equivalent to the maximum value representable in that
type minus E
".
Because unsigned char
type is narrower than (as it requires fewer bytes) int
type, - implicit type promotion performed by abstract machine(compiler) and value of variable c
is promoted to int
at the time of compilation (before application of the complement operation ~
). It is required for the correct execution of the program because ~
need an integer operand.
- Some operators (the unary operator
~
, and the binary operators <<
, >>
, &
, ^
, and |
,
collectively described as bitwise operators) are required to have operands that have
integer type. These operators yield values that depend on the internal representations of
integers, and have implementation-defined and undefined aspects for signed types.
Compilers are smart-enough to analyze expressions, checks semantics of expressions, perform type checking and arithmetic conversions if required. That's the reason that to apply ~
on char
type we don't need to explicitly write ~(int)c
— called explicit type casting (and do avoid errors).
Note:
Value of c
is promoted to int
in expression ~c
, but type of c
is still unsigned char
- its type does not. Don't be confused.
Important: result of ~
operation is of int
type!, check below code (I don't have vs-compiler, I am using gcc):
#include<stdio.h>
#include<stdlib.h>
int main(void){
unsigned char c = 4;
printf(" sizeof(int) = %zu,\n sizeof(unsigned char) = %zu",
sizeof(int),
sizeof(unsigned char));
printf("\n sizeof(~c) = %zu", sizeof(~c));
printf("\n");
return EXIT_SUCCESS;
}
compile it, and run:
$ gcc -std=gnu99 -Wall -pedantic x.c -o x
$ ./x
sizeof(int) = 4,
sizeof(unsigned char) = 1
sizeof(~c) = 4
Notice: size of result of ~c
is same as of int
, but not equals to unsigned char
— result of ~
operator in this expression is int
! that as mentioned 6.5.3.3 Unary arithmetic operators
- The result of the unary
-
operator is the negative of its (promoted) operand. The integer
promotions are performed on the operand, and the result has the promoted type.
Now, as @haccks also explained in his answer -that result of ~c
on 32-bit machine and for value of c = 4
is:
1111 1111 1111 1111 1111 1111 1111 1011
in decimal it is -5
— that is the output of your second code!
In your first code, one more line is interesting to understand b = ~c;
, because b
is an unsigned char
variable and result of ~c
is of int
type, so to accommodate value of result of ~c
to b
result value (~c) is truncated to fit into the unsigned char type as follows:
1111 1111 1111 1111 1111 1111 1111 1011 // -5 & 0xFF
& 0000 0000 0000 0000 0000 0000 1111 1111 // - one byte
-------------------------------------------
1111 1011
Decimal equivalent of 1111 1011
is 251
. You could get same effect using:
printf("\n ~c = %d", ~c & 0xFF);
or as suggested by @ouah in his answer using explicitly casting.