uint32_t a = 0xFF << 8;
uint32_t b = 0xFF;
uint32_t c = b << 8;
I'm compiling for the Uno (1.0.x and 1.5) and it would seem obvious that a
and c
should be the same value, but they are not... at least not when running on the target. I compile the same code on the host and have no issues.
Right shift works fine, left shift only works when I'm shifting a variable versus a constant.
Can anyone confirm this?
I'm using Visual Micro with VS2013. Compiling with either 1.0.x or 1.5 Arduino results in the same failure.
EDIT:
On the target:
A = 0xFFFFFF00
C = 0x0000FF00
[Credit goes to Mats Petersson]
Using a cast operator to force the compiler to treat the 0xFF as a uint32_t addresses the issue. Seems like the Arduino xcompiler treats constants a little differently since I've never had cast before a shift.
Thanks!
The problem is related to the signed/unsigned implicit cast.
With
uint32_t a = 0xFF << 8;
you mean0xFF
is declared; it is asigned char
;0xFFFFFFFF
;a = 0xFFFFFF00
.If you want to reproduce the same behaviour, try this code:
The result is
Or, in the other way, if you write
you get that
a = 0x0000FF00
.There are just two weird things with the compiler:
uint32_t a = (unsigned char)0xFF << 8;
returns a = 0xFFFFFF00uint32_t a = 0x000000FF << 8;
returns a = 0xFFFFFF00 too.Maybe it's a wrong cast in the compiler....
EDIT:
As phuclv pointed out, the above explanation is slightly wrong. The correct explanation is that, with
uint32_t a = 0xFF << 8;
, the compiler does this operations:0xFF
is declared; it is anint
;0xFF00
; it was an int, so it is negativeuint32_t
. Since it was negative,1
s are prepended, resulting in a0xFFFFFF00
The difference with the above explanation is that if you write
uint32_t a = 0xFF << 7;
you get0x7F80
rather than0xFFFFFF80
.This also explains the two "weird" things I wrote in the end of the previous answer.
For reference, in the thread linked in the comment there are some more explanations on how the compiler interpretes literals. Particularly in this answer there is a table with the types the compiler assigns to the literals. In this case (no suffix, hexadecimal value) the compiler assigns this type, according to what is the smallest type that fits the value:
int
unsigned int
long int
unsigned long int
long long int
unsigned long long int
This leads to some more considerations:
uint32_t a = 0x7FFF << 8;
this means that the literal is interpreted as a signed integer; the promotion to the bigger integer extends the sign, and so the result is0xFFFFFF00
uint32_t b = 0xFFFF << 8;
the literal in this case is interpreted as an unsigned integer. The result of the promotion to the 32-bit integer is therefore0x0000FF00
The most important thing here is that in Arduino int is a 16-bit type. That'll explain everything
For
uint32_t a = 0xFF << 8
: 0xFF is of typeint
1.0xFF << 8
results in 0xFF00 which is a signed negative value in 16-bit int2. When assigning theint
value to auint32_t
variable again it'll be sign-extended 3 when upcasting, thus the result becomes 0xFFFFFF00UFor the following lines
0xFF is positive in 16-bit int, therefore
b
also contains 0xFF. Then shifting it left 8 bits results in 0x0000FF00, becauseb << 8
is anuint32_t
expression. It's wider thanint
so there's no promotion toint
happening hereSimilarly with
uint32_t a = (unsigned)0xFF << 8
the output is 0x0000FF00 because the positive 0xFF when converted tounsigned int
is still positive. Upcastingunsigned int
touint32_t
does a zero extension, but the sign bit is already zero so even if you doint32_t b = 0xFF; uint32_t c = b << 8
the high bits are still zero. Same to the "weird"uint32_t a = 0x000000FF << 8
. Instead of (unsigned)0xFF you can just use the exact equivalent version (but shorter)0xFFU
OTOH if you declare b as
uint8_t b = 0xFF
orint8_t b = 0xFF
then things will be different, integer promotion occurs and the result will be similar to the first line (0xFFFFFF00U). And if you cast 0xFF tosigned char
like thisthen upon promoting to int it'll be sign-extended to 0xFFFF. Similarly casting it to
int32_t
oruint32_t
will result in a sign-extension fromsigned char
to the 32-bit wide value 0xFFFFFFFFIf you cast to
unsigned char
like inuint32_t a = (unsigned char)0xFF << 8;
instead then the(unsigned char)0xFF
will be promoted to int using zero extension4, therefore the result will be exactly the same asuint32_t a = 0xFF << 8;
In summary: When in doubt, consult the standard. The compiler rarely lies to you
1 Type of integer literals not int by default?
2 Strictly speaking shifting into sign bit like that is undefined behavior
3 The rule is to add UINT_MAX + 1
4A cast will always preserve the input value if the value fits in the target type, so casting a signed type to a wider signed type will be done by a sign-extension, and casting an unsigned type to a wider type will be done by a zero-extension