I have a unsigned short
(which is 16 bit on the target platforms)
It contains two 8-bit signed values, one in the lower byte, one in the higher byte.
#include <vector>
#include <iostream>
int main() {
unsigned short a = 0xE00E;
signed char b = a & 0xFF;
signed char c = ((a >> 8) & 0xFF);
std::cout << (int)b << std::endl;
std::cout << (int)c << std::endl;
}
Is this portable, or am I relying on platform dependent behaviour here?
On all major compilers (gcc, msvc, clang), the result is 14
and -32
, which is the expected output.
Disclaimer: I am no language lawyer
Is this portable, or am I relying on platform dependent behaviour here?
Since there is no version specified, I used last draft.
unsigned short
hold 0xE00E
and signed char
can hold 8 bits?a & 0xFF
and ((a >> 8) & 0xFF)
are transformed into signed char
?signed char
is transformed into int
?unsigned short
hold 0xE00E
?Type | Minimum width
signed char 8
short int 16
int 16
long int 32
long long int 64
Source : https://eel.is/c++draft/basic.fundamental
a & 0xFF
and ((a >> 8) & 0xFF)
are transformed into signed char
?In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied
unsigned char or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
Source: https://en.cppreference.com/w/cpp/language/implicit_conversion Integral promotion
AFAIU, in a & 0xFF
, a
can be an int
or unsigned int
.
If sizeof(unsigned short) == sizeof(int)
a
will be an unsigned int
and an
int
otherwise.
If the types are the same, that type is the common type.
If the unsigned type has conversion rank greater than or equal to the rank of the signed type, then the operand with the signed type is implicitly converted to the unsigned type.
Source: https://en.cppreference.com/w/c/language/conversion Usual arithmetic conversions
Now we need to go to signed char
from int
or unsigned int
Otherwise, the result is the unique value of the destination type that is congruent to the source integer modulo 2N, where N is the width of the destination type.
Source: https://eel.is/c++draft/conv.integral#3
Note: is implementation-defined until C++20 (source https://en.cppreference.com/w/cpp/language/implicit_conversion)
And the memory representation of an negative signed:
An unsigned integer type has the same object representation, value representation, and alignment requirements ([basic.align]) as the corresponding signed integer type. For each value x of a signed integer type, the value of the corresponding unsigned integer type congruent to x modulo 2N has the same value of corresponding bits in its value representation. [Example 1: The value −1 of a signed integer type has the same representation as the largest value of the corresponding unsigned type. — end example]
Source: https://eel.is/c++draft/basic.fundamental#3
All good.
signed char b = a & 0xFF;
signed char c = ((a >> 8) & 0xFF);
Are defined and what you expect.
signed char
is transformed into int
?It's https://eel.is/c++draft/conv.integral#3 again. And it wont modified the value.
Is bitshifting from an unsigned to a signed smaller type portable?
Yes
The conversion unsigned to signed is implementation defined. To prevent this to happen we need to
static_assert(sizeof(unsigned short) < sizeof(int));
And the code is fully portable.