Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Result of type cast and bitwise operation in C depends on the order

I was trying to print the minimum of int, char, short, long without using the header file <limit.h>. So bitwise operation will be a good choice. But something strange happened.

The statement

printf("The minimum of short: %d\n", ~(((unsigned short)~0) >> 1));

gives me

The minimum of short: -32768

But the statement

printf("The minimum of short: %d\n", ~((~(unsigned short)0) >> 1));

gives me

The minimum of short: 0

This phenomenon also occurs in char. But it does not occur in long, int. Why does this happen?

And it is worth mentioning that I use VS Code as my editor. When I moved my cursor on unsigned char in the statement

printf("The minimum of char: %d\n", (short)~((~(unsigned char)0) >> 1));

It gives me a hint (int) 0 instead of (unsigned char)0, which I expected. Why does this happen?

like image 216
Rivers Shall Avatar asked Jun 28 '18 06:06

Rivers Shall


People also ask

Does the order of bitwise operations matter?

It depends on how many logical statements there are. When there is only one logical statement, it's always going to evaluate the same way, because it's just a single statement. When there are two logical statements, order does not matter, because we can only apply one logical statement & (and) or | (or) between them.

What does bitwise and do to a sequence of bits?

The bitwise AND may be used to clear selected bits (or flags) of a register in which each bit represents an individual Boolean state. This technique is an efficient way to store a number of Boolean values using as little memory as possible. Because 6 AND 1 is zero, 6 is divisible by two and therefore even.

What does bitwise and return in C?

The & (bitwise AND) in C or C++ takes two numbers as operands and does AND on every bit of two numbers. The result of AND is 1 only if both bits are 1. The | (bitwise OR) in C or C++ takes two numbers as operands and does OR on every bit of two numbers. The result of OR is 1 if any of the two bits is 1.

Which datatype uses bitwise operators?

There are four bitwise operators in IDL: AND, NOT, OR, and XOR. For integer operands (byte, signed- and unsigned-integer, longword, and 64-bit longword data types), bitwise operators operate on each bit of the operand or operands independently.


1 Answers

First of all, none of your code is really reliable and won't do what you expect.

printf and all other variable argument length functions have a dysfunctional "feature" called the default argument promotions. This means that the actual type of the parameters passed undergo silent promotion. Small integer types (such as char and short) get promoted to int which is signed. (And float gets promoted to double.) Tl;dr: printf is a nuts function.

Therefore you can cast between various small integer types all you want, there will still be a promotion to int in the end. This is no problem if you use the correct format specifier for the intended type, but you don't, you use %d which is for int.

In addition, the ~ operator, like most operators in C, performs implicit integer promotion of its operand. See Implicit type promotion rules.


That being said, this line ~((~(unsigned short)0) >> 1) does the following:

  • Take the literal 0 which is of type int and convert to unsigned short.

  • Implicitly promote that unsigned short back to int through implicit integer promotion.

  • Calculate the bitwise complement of the int value 0. This is 0xFF...FF hex, -1 dec, assuming 2's complement.

  • Right shift this int by 1. Here you invoke implementation-defined behavior upon shifting a negative integer. C allows this to either result in a logical shift = shift in zeroes, or arithmetic shift = shift in a sign bit. Different result from compiler to compiler and non-portable.

    You get either 0x7F...FF in case of logical shift or 0xFF...FF in case of arithmetic shift. In this case it seems to be the latter, meaning you still have decimal -1 after shift.

  • You do bitwise complement of the 0xFF...FF = -1 and get 0.

  • You cast this to short. Still 0.

  • Default argument promotion convert it to int. Still 0.

  • %d expects a int and therefore prints accordingly. unsigned short is printed with %hu and short with %hd. Using the correct format specifier should undo the effect of default argument promotion.

Advice: study implicit type promotion and avoid using bitwise operators on operands that have signed type.

To simply display the lowest 2's complement value of various signed types, you have to do some trickery with unsigned types, since bitwise operations on their signed version are unreliable. Example:

int shift = sizeof(short)*8 - 1;  // 15 bits on sane systems
short s = (short) (1u << shift);
printf("%hd\n", s);

This shifts an unsigned int 1u 15 bits, then converts the result of that to short, in some "implementation-defined way", meaning on two's complement systems you'll end up converting 0x8000 to -32768.

Then give printf the right format specifier and you'll get the expected result from there.

like image 170
Lundin Avatar answered Oct 10 '22 08:10

Lundin