Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ - Bit-wise not of uchar produces int

I am surprised by C++'s behavior when applying bit-wise not to an unsigned char.

Take the binary value 01010101b, which is 0x55, or 85. Applying bit-wise not on an eight bit representation should yield 10101010b, which is 0xAA, or 170.

However, I cannot reproduce the above in C++. The following simple assertion fails.

assert(static_cast<unsigned char>(0xAAu) == ~static_cast<unsigned char>(0x55u));

I printed the values of 0x55, 0xAA, and ~0x55 (as uchar) with the following code. And it reveals that the bit-wise not does not do what I expect it to do.

std::cout << "--> 0x55: " << 0x55u << ", 0xAA: " << 0xAAu << ", ~0x55: "
     << static_cast<unsigned>(~static_cast<unsigned char>(0x55u)) << std::endl;

--> 0x55: 85, 0xAA: 170, ~0x55: 4294967210

The number that is printed for ~0x55 is equal to 11111111111111111111111110101010b, which is the 32-bit bit-wise not of 0x55. So, the ~ operator is operating on 32-bit integers even if I explicitly cast the input to an unsigned char. Why is that?

I applied another test to see what type the ~ operator returns. And it turns out to be int on an unsigned char input:

template <class T>
struct Print;

// inside main()    
Print<decltype(~static_cast<unsigned char>(0x55))> dummy;

Yields the following compiler error, which indicates, that the result is of type int.

error: implicit instantiation of undefined template 'Print<int>'
    Print<decltype(~static_cast<unsigned char>(0x55u))> dummy;

What am I doing wrong? Or, how do I get C++ to produce 0xAA from ~0x55?

Full code is here

like image 827
Lemming Avatar asked Sep 29 '14 13:09

Lemming


People also ask

How many bits is a unsigned char?

The smallest group of bits the language allows use to work with is the unsigned char , which is a group of 8 bits.

How many bits is a char in C?

In practice, char is usually 8 bits in size and short is usually 16 bits in size (as are their unsigned counterparts).

How do you set a bit in a number?

To set any bit we use bitwise OR | operator. As we already know bitwise OR | operator evaluates each bit of the result to 1 if any of the operand's corresponding bit is set (1).


2 Answers

Integral promotions are performed on the operand of ~ we can see this by going to the draft C++ standard section 5.3.1 Unary operators which says (emphasis mine):

The operand of ˜ shall have integral or unscoped enumeration type; the result is the one’s complement of its operand. Integral promotions are performed. The type of the result is the type of the promoted operand [...]

and the integral promotions are covered in section 4.5 Integral promotions and say:

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type;

For completeness, to see that unsigned char rank is less than the rank of int we can go to section 4.13 Integer conversion rank which says:

The rank of a signed integer type shall be greater than the rank of any signed integer type with a smaller size.

and:

The rank of char shall equal the rank of signed char and unsigned char.

One solution would be to assign the result to an unsigned char which, this is safe since you don't have to worry about signed integer overflow.

As Ben Voigt points out it would compliant to have a system where sizeof (int) == 1 and CHAR_BIT >= 32. In which case the rank of unsigned char woudl not be less than int and therefore the promotion would be to unsigned int. We do not know of any systems that this actually occurs on.

like image 51
Shafik Yaghmour Avatar answered Sep 22 '22 13:09

Shafik Yaghmour


The answer about integral promotion is correct.

You can get the desired results by casting and NOTting in the right order:

assert(static_cast<unsigned char>(0xAAu) == static_cast<unsigned char>(~0x55u));
like image 40
StilesCrisis Avatar answered Sep 23 '22 13:09

StilesCrisis