In (int16_t)dataReceived[3] << 8, the uint8_t dataReceived[3] is converted to int16_t. This conversion operates by value; the bits representing it are irrelevant. Since all uint8_t values are representable in int16_t, the result is a value from 0 to 255, inclusive. In the case you ask about, the value is 255.
Then the integer promotions are performed on the left operand of <<. If int is 16 bits in this C implementation, then either int16_t is an alias for int or it is converted to int. (Exception: If the C implementation does not use two’s complement for int, the behavior is more complicated. However, this answer will not go into that.) Then the shift operation is performed. Shifting 255 left 8 bits mathematically yields 65,280. However, this is not representable in a 16-bit int, so the behavior is undefined, per C 2018 6.5.7 4.
If int is wider than 16 bits, then the integer promotions promote int16_t to int, the shift does not overflow, and the result is some value in 0 to 65,280, inclusive, that is a multiple of 256.
Then ORing in dataReceived[4] merges its bits into this value. The result is a value from 0 to 65,535, inclusive.
Then the cast to (int16_t) is applied. If the value is 0 to 32,767, it is the result. Otherwise, the value cannot be represented in int16_t. Then the result is implementation-defined or an implementation-defined signal is raised, per C 2018 6.3.13. GCC and Clang define the result to wrap modulo 216, so this will produce the result you desire. Other compilers might not produce the result you desire.
If int is 16 bits, you could avoid the overflow in shifting by casting dataReceived[3] to int32_t instead of to int16_t. Then the integer promotions will not change it, and the int32_t value will be shifted.
However, since we still need to deal with the overflow in the final conversion to int16_t, there is another method:
uint16_t tu = (uint16_t) dataReceived[3] << 8 | dataReceived[4];
int16_t ti;
memcpy(&ti, &tu, sizeof ti);
val_x += ti;
This:
- Converts
dataReceived[3] to uint16_t so the shift will not overflow.
- Completes the shift and merge and stores the result in a temporary
uint16_t object.
- Copies the bits of the
uint16_t object into an int16_t object to reinterpret them as a 16-bit two’s complement integer.
- Adds the integer to
val_x.
The following code is effectively the same and avoids the named temporaries but may be less familiar and harder to read for novice C programmers:
val_x += (union { uint16_t u; int16_t i; }) { (uint16_t) dataReceived[3] | dataReceived[4] } .i;
val_x +=-part, remove the| dataReceived[4]-part, replacedataReceived[3]with a scalar variable of the type you actually care about. Can you understand your problem then?