1
unsigned char a = 10,b;
b = ~a;
printf(" %d \n ",b);

output:

245

same program if I use int instead of unsigned char the o/p changes to -11

SajithP
  • 592
  • 8
  • 19
Kiran HS
  • 27
  • 3

5 Answers5

6

The variable a contains the unsigned char value 10. Assuming an orthodox setup with 8-bit char and 32-bit int types, then the bits in a are:

0000 1010

The value stored in b is the bitwise inverse of what's in a, which means it contains the following bits, which correspond to 245 decimal:

1111 0101

When an unsigned char is passed to printf():

printf("a = %d\n", a);
printf("b = %d\n", b);

the types shorter than int (the character types and short types — I'm assuming that short is a 16-bit type, which is usually but not necessarily the case) are automatically promoted to int. Since the source type is unsigned char, the value is converted to a positive int value — 10 and 245 in the example code.

Change the type from unsigned char to int and things alter.

a:  0000 0000 0000 0000 0000 0000 0000 1010
b:  1111 1111 1111 1111 1111 1111 1111 0101

and the bit pattern in b corresponds to -11 because of the way 2's complement arithmetic (storage) works. And when these versions of a and b are passed to printf(), there is no conversion necessary, so printf() sees 10 and -11 as the values, and prints accordingly.

The key point is the promotion operation.

In the C standard, it says:

6.5.2.2 Function calls

¶7 If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

Of course, the declared type of printf() is:

int printf(const char * restrict format, ...);

So, the values a and b come after the last declared parameter and are subject to 'default argument promotions'. They're specified (in part) in paragraph 6:

¶6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. …

You have to go back a bit further to find integer promotions:

6.3.1 Arithmetic operands

6.3.1.1 Boolean, characters and integers

¶2 The following may be used in an expression wherever an int or unsigned int may be used:

  • An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
  • A bit-field of type _Bool, int, signed int, or unsigned int.

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.58) All other types are unchanged by the integer promotions.

¶3 The integer promotions preserve value including sign. As discussed earlier, whether a ‘‘plain’’ char is treated as signed is implementation-defined.

The concept of rank is defined in §6.3.1.1 ¶1, and I'm not going to quote it (it's fairly long and very detailed).

The key point is that smaller types (like char) are promoted to int, and preserve the value including the sign. And what you're seeing exactly matches that.


Test code

#include <stdio.h>

int main(void)
{
    unsigned char a1 = 10;
    unsigned char b1 = ~a1;
    printf("a1 = %d, b1 = %d\n", a1, b1);

    int a2 = 10;
    int b2 = ~a2;
    printf("a2 = %d, b2 = %d\n", a2, b2);

    return 0;
}

Output:

a1 = 10, b1 = 245
a2 = 10, b2 = -11
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 2
    What's important is the integer promotion caused by `~` in itself, because this causes a silent change of signedness. This happens before the printf default argument promotion. – Lundin Aug 11 '17 at 06:40
  • @FilipKočica: I'm not sure what you did to get 245 printed. I've added a 'test code' section at the end of my answer showing what I got (on a Mac running macOS Sierra 10.12.6 with GCC 7.1.0, though most machines you try it on will produce the same result anyway). – Jonathan Leffler Aug 11 '17 at 06:48
  • Ah sure i thought that you are explaining that `char` to `int` promotion from `8b` to `32b`. My bad. – kocica Aug 11 '17 at 06:52
6

Whenever ~ is used on an operand of small integer type, such as char, unsigned char, short etc, that operand is always implicitly promoted to type int. This is known as the integer promotion rule.

Meaning that no matter what char type a in your example is, it will get promoted to int which is a signed type. The expression ~a is equivalent to ~(int)10. The result will be (assuming 32 bit CPU) 0xFFFFFFF5, represented in two's complement as -11.

However, when you store this number back into an unsigned char, it will get converted to an unsigned type with value 245. When you declare b as int, this conversion doesn't happen and the value remains -11.

The above is the reason why ~ is a rather dangerous operator to use. In more complex expressions it can create very subtle bugs caused by the silent change of signedness.

Lundin
  • 195,001
  • 40
  • 254
  • 396
5

Unsigned char

a

|128|64|32|16|8|4|2|1|
|0  |0 |0 |0 |1|0|1|0|

equals 10

~ is bit negation

(unsigned char)~a

|128|64|32|16|8|4|2|1|
|1  |1 |1 |1 |0|1|0|1|

equals 245

Since 8b char is smaller than 32b int, the value is extended to 32b but the sign is preserved when calling printf().

Int

Two's complement is usually used for signed operations. Let's expect that sizeof(int) equals 4B (32bit's). (Sizeof int depend's on architecture.)

10 in 4B

|0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |1|0|1|0|

~10 in 4B

|1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |0|1|0|1|

To get value in decimal

  • ~ Negate value
  • Add 1
  • Change sign

So you have

- |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |1|0|1|1|

and it equals -11 in decimal

What is the use of unsigned char in bitwise negation operation?

By using unsigned char you got 8 bit's unsigned which mean's values 0..255. (2^8 -1)

So for signed char you've got values -127..127. (+/-)2^7 -1

The unsigned char simply means: Use the most significant bit instead of treating it as a bit flag for +/- sign when performing arithmetic operations.

kocica
  • 6,412
  • 2
  • 14
  • 35
  • 1
    What you are missing to explain is that the `~` doesn't actually work on `unsigned char` type at all, but on `int` type, because of _integer promotion_, which is central to this question. Meaning that the code `if(~a < 0) puts("oops")` will print "oops". Had the op however written something like `int b = (unsigned char)~a;` the result would have remained 245. – Lundin Aug 11 '17 at 06:31
0

int variables store 32 bits and the first bit stands for the sign of number. If the bit is on, then the number is negative.

the negative numbers are represented as follows:

-1 | 11111...111
-2 | 11111...110
-3 | 11111...101
-4 | 11111...100

The sequence is almost the same as with positive integers, but in reversed order.

  • 2
    An `int` could be as narrow as 16 bits, or could be wider than 32 bits, according to the Standard. – ad absurdum Aug 11 '17 at 05:57
  • Yes, David Bowling, you are right. But I havn't ever met the pc(or compiler) with such standard. I think the majority of all PCs are int - 32 bit, long long - 64 bit. – Kosimov Sukhrob Aug 11 '17 at 06:01
  • 2
    Note: The majority of processors made in 2017 are not for PCs. Billions per year are used in other devices. [e.g.](https://qz.com/377581/the-chart-that-shows-how-everything-is-becoming-a-computer/) At least 100s of millions are 16-bit devices. – chux - Reinstate Monica Aug 11 '17 at 10:47
0

In C, int and char has to types

  1. Signed
  2. Unsigned

If user gives int [16 bits] or char [8 bits], then compiler takes by default as signed. for unsigned, Users need to mention explicitly.

unsigned char ranges from 0 to 255 [8 bits].

signed char ranges from -128 to 0 to 127 [8 bits].

As per your program,

Case 1:

a is unsigned char, the output 245 is with in the range of 0 to 255. so you got output as 245.

    0, 1, 2, 3, 4, ..................... 254, 255
    |                                          |
    |<- <- <- <- <- <- <- <- <- <- <- <- <- <- |  

    example:
          unsigned char a = 257;       // more than 255.
          printf("a = %d", a);          

         **Output** : a = 1.        // [ 0,1,...255,0 [256th], **1** [257th]

         unsigned char a = 129;       // with in range of 255.
          printf("a = %d", a);          

         **Output** : a = 129.                   

Case 2:

a is signed char, the same output 245 is starting from 0 to 127 and then starts from -128, -127, -126, ..... -11 (245th Position) and prints -11.

 -128, -127, -126, ..... -11, -10, .. -1, 0, 1, 2, 3, .....126, 127
   |                                                             |       
   |<- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <-<-|

 example:
          char a = 257;          // more than 255.
          printf("a = %d", a);          

         **Output** : a = 1.     // [ 0,1,...127,-128,-127,....-1, 0 [256th], **1** [257th].

          char a = 129;              // with in range of 255.
          printf("a = %d", a);          

         **Output** : a = -127.      // [ 0,1,...127,-128 [128th], **-127** [129th].             

The same case is applicable for int data type also.

Eswaran Pandi
  • 602
  • 6
  • 10