This answer will assume a mainstream system with 8 bit character types, 16 bit short
and 32 bit int
(this is the case for all mainstream 32 and 64 bitters in the real world).
First check out Implicit type promotion rules. How this works in this particular case:
Each operand in C comes with it's own little line stating how implicit promotions are handled. In case of |
, we can peek at C17 6.5.12 "the bitwise inclusive OR operator":
Constraints
Each of the operands shall have integer type.
Semantics
The usual arithmetic conversions are performed on the operands.
As we learned from the link at the top of this post, the integer promotions are part of the usual arithmetic conversions. So in the expression res = mask1 | mask2;
, both operands are small integer types and therefore promoted to int
which is signed. Which is a bit unfortunate since we want to avoid bitwise arithmetic using signed operands like the plague, though in this specific case it makes no difference. Instead of 0x8055 and 0x55 we will get 0x00008055 and 0x00000055 - basically just zero padding.
Thus it is 100% equivalent to res = (int)mask1 | (int)mask2;
and the result of mask1 | mask2
is of type int
.
Next up this is stored in res
which is of type unsigned short
. What happens then is "conversion during assignment", 6.5.16:
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.
The specific rules for what this conversion entails is found in C17 6.3.1.3:
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type
This conversion works like modulus, or if you will a binary truncation of the raw value where the most significant bytes are simply discarded.
In this specific case we have an int
with value 0x00008055
and it is converted to an unsigned short
with value 0x8055
.
A curious note regarding "conversion during assignment" is that is also happens on all of these lines:
unsigned char mask1 = 0x55; //01010101
unsigned short int mask2 = 0x8055;//1000000001010101
unsigned short int res = 0;
The numbers here, 0x55
and so forth, are formally called integer constants. Integer constants in C have a type picked based on various intricate rules (C17 6.4.4.1) - I won't mention them here but for now we can note that an integer constant can never be of a smaller type than int
. So during all of the above initializations, we have implicit conversion from int
to the type of the left operand.