58

I was reading a chapter on bitwise operators, I came across 1's complement operator program and decided to run it on Visual C++.

int main ()
{
   unsigned char c = 4, d;
   d = ~c;
   printf("%d\n", d);
}

It gives the valid output: 251

Then instead of using d as a variable to hold the value of ~c, I decided to directly print the value of ~c.

int main ()
{
   unsigned char c=4;
   printf("%d\n", ~c);
}

It gives the output -5.

Why didn't it work?

JB.
  • 40,344
  • 12
  • 79
  • 106
Sanketssj5
  • 655
  • 5
  • 17
  • 4
    Hint : 1's Complement of `4` is equivalent to two's complement representation of `-5` – Vagish Feb 17 '15 at 10:55
  • 3
    it's [complement](http://en.wikipedia.org/wiki/Ones%27_complement), not [compliment](http://dictionary.cambridge.org/dictionary/british/compliment) – phuclv Feb 18 '15 at 15:46
  • 2
    @LưuVĩnhPhúc your complement deserves a compliment. – Déjà vu Mar 02 '15 at 22:57

6 Answers6

55

In this statement:

printf("%d",~c);

the c is converted to int1 type before ~ (bitwise complement) operator is applied. This is because of integer promotions, that are invoked to operand of the ~. In this case an object of unsigned char type is promoted to (signed) int, which is then (after ~ operator evaluation) used by printf function, with matching %d format specifier.

Notice that default argument promotions (as printf is a variadic function) does not play any role here, as object is already of type int.

On the other hand, in this code:

unsigned char c = 4, d;
d = ~c;
printf("%d", d);

the following steps occur:

  • c is a subject to integer promotions because of ~ (in the same way, as described above)
  • ~c rvalue is evaluated as (signed) int value (e.g. -5)
  • d=~c makes an implicit conversion from int to unsigned char, as d has such type. You may think of it as the same as d = (unsigned char) ~c. Notice that d cannot be negative (this is general rule for all unsigned types).
  • printf("%d", d); invokes default argument promotions, thus d is converted to int and the (nonnegative) value is preserved (i.e. the int type can represent all values of unsigned char type).

1) assuming that int can represent all values of the unsigned char (see T.C.'s comment below), but it is very likely to happen in this way. More specifically, we assume that INT_MAX >= UCHAR_MAX holds. Typically the sizeof(int) > sizeof(unsigned char) holds and byte consist of eight bits. Otherwise the c would be converted to unsigned int (as by C11 subclause §6.3.1.1/p2), and the format specifier should be also changed accordingly to %u in order to avoid getting an UB (C11 §7.21.6.1/p9).

Community
  • 1
  • 1
Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • 9
    But then this also happens in `d=~c`. The real diffrence then is the conversion (back) to `unsigend char` that happens in `d=~c`, but not in the `printf` call. – Marc van Leeuwen Feb 17 '15 at 12:44
  • @MarcvanLeeuwen: The `d` has type `unsigned char`, so it cannot be negative (even after conversion to `int`, because of `printf`'s _default argument promotions_). In fact, you are right that in `d=~c` integer promotion also have taken place (in the same way, as I described in answer above), but assignment converts `int` to `unsigned char` back again. On the other hand, in the second case, `printf` function takes `int` argument "as-is" (i.e. `%d` format specifier is correct), so _default arguments promotions_ do nothing there. – Grzegorz Szpetkowski Feb 17 '15 at 12:56
  • For further readers: I updated my answer to reflect Marc's observion. You may find the original (shorter) answer in revision history. Hope everything is clear now. – Grzegorz Szpetkowski Feb 17 '15 at 19:36
  • Note that in some systems `unsigned char` could be promoted to `unsigned int` by the integer promotions (e.g., if `sizeof(int) == 1`), in which case the `printf` would invoke undefined behavior because it used the wrong specifier. – T.C. Feb 18 '15 at 15:08
  • @T.C.: You are right, but let's assume (for simplicity) that we live in world where one byte consist of eight bits. I know that the Standard does not enforce it, but for instance the POSIX does. – Grzegorz Szpetkowski Feb 18 '15 at 15:18
27

char is promoted to int in printf statement before the operation ~ in second snippet. So c, which is

0000 0100 (2's complement)  

in binary is promoted to (assuming 32-bit machine)

0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x  

and its bit-wise complement is equal to the two's complement of the value minus one (~x = −x − 1)

1111 1111 1111 1111 1111 1111 1111 1011  

which is -5 in decimal in 2's complement form.

Note that the default promotion of char c to int is also performed in

d = ~c;

before complement operation but the result is converted back to unsigned char as d is of type unsigned char.

C11: 6.5.16.1 Simple assignment (p2):

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

and

6.5.16 (p3):

The type of an assignment expression is the type the left operand would have after lvalue conversion.

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
17

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:

§ 6.5.3.3 Unary arithmetic operators

  1. 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.

§ 6.5 Expressions

  1. 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:

  1. 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.

  2. 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

    1. 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.

Community
  • 1
  • 1
Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
  • 1
    @haccks thanks, yes I mostly come here for learning than participating and presently I am mostly working in python,django, web stuffs. – Grijesh Chauhan Feb 17 '15 at 17:54
  • 2
    +int(PI/3) for actually quoting the relevant spec parts... no other answer actually explains *why* the promotion happens here! this->`Some operators (the unary operator ~, and the binary operators <<, >>, &, ^, and |, collectively described as bitwise operators) are required to have operands that have integer type.` made my day today. –  Feb 18 '15 at 09:25
  • 1
    @vaxquis thanks, - by the time I came to this post I notices that most answers were explaining "how results are different" , so I decided to add my answer and emphasize reason of "why so", -- at the time you read my answer I was missing one more relevant from draft, now added. – Grijesh Chauhan Feb 18 '15 at 15:09
12

When applying the ~ operator to c it gets promoted to int, the result is an int as well.

Then

  • in the 1st example the result gets converted to unsigned char and then promoted to signed int and printed.
  • in the 2nd example the result gets printed as signed int.
alk
  • 69,737
  • 10
  • 105
  • 255
10

It gives the op -5. why it didn't work?

Instead of:

printf("%d",~c);

use:

printf("%d", (unsigned char) ~c);

to get the same result as in your first example.

~ operand undergoes integer promotion and default argument promotion are applied to argument of variadic functions.

ouah
  • 142,963
  • 15
  • 272
  • 331
8

Integer promotion, from the standard:

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, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94