Reputation: 7435
I want to use a uint32_t
to store 4 separate uint8_t
values, and be able to read/write each of them individually.
Is it safe to do either of the following to set the value of each 8-bit range, and if so, which is better (faster, more portable)? I am not necessarily trying to set them all at once, I am only illustrating how to set each 8-bit value at any point in time.
uint32_t x = ...;
Option 1:
((uint8_t *)(&x))[0] = a;
((uint8_t *)(&x))[1] = b;
((uint8_t *)(&x))[2] = c;
((uint8_t *)(&x))[3] = d;
Option 2:
x = (x & 0xFFFFFF00) | (uint32_t) a;
x = (x & 0xFFFF00FF) | (uint32_t) b << 8;
x = (x & 0xFF00FFFF) | (uint32_t) c << 16;
x = (x & 0x00FFFFFF) | (uint32_t) d << 24;
Upvotes: 3
Views: 2079
Reputation: 51906
Your initial revision had a correct albeit roundabout approach for option 2, which was
// a, b, c, and d are of initialized and of type uint8_t
uint32_t x = ...;
x = (x & 0xFFFFFF00) | (uint32_t) a;
x = (x & 0xFFFF00FF) | (uint32_t) b << 8;
x = (x & 0xFF00FFFF) | (uint32_t) c << 16;
x = (x & 0x00FFFFFF) | (uint32_t) d << 24;
This revision for option 2 is wrong:
uint32_t x = ...;
x |= (uint32_t) a;
x |= (uint32_t) b << 8;
x |= (uint32_t) c << 16;
x |= (uint32_t) d << 24;
Even when x
is initialized it's still wrong because you're not setting the 8 bit ranges, you're ORing them.
The correct approach would be
// a, b, c, and d are of initialized and of type uint8_t
uint32_t x = (uint32_t) a;
x |= (uint32_t) b << 8;
x |= (uint32_t) c << 16;
x |= (uint32_t) d << 24;
Or more succinctly
// a, b, c, and d are of initialized and of type uint8_t
uint32_t x =
(uint32_t) a
| (uint32_t) b << 8
| (uint32_t) c << 16
| (uint32_t) d << 24;
The issue with option 1 is that it assumes the endianness of uint32_t
to be LSB first and is therefore not a portable solution.
After receiving clarification about the question you're asking, your initial revision (the first code block in this answer) is the correct approach. It leaves the remaining 24 bits untouched while setting a particular 8 bit range to the uint8_t
value on the RHS.
Upvotes: 3
Reputation: 67638
Universal function (for pos8 16 and 24) :
uint32_t setb(uint32_t val, uint8_t a, int pos)
{
val &= ~(0xffUL << pos);
val |= (uint32_t)a << pos;
return val;
}
If you do not like shifts:
uint32_t f(uint8_t a, uint8_t b,uint8_t c,uint8_t d) {
return a + 0x100UL * b + 0x10000UL * c + 0x1000000UL * d;
}
any good optimizing compiler will generate very efficient code:
gcc ARM
f:
add r0, r0, r2, lsl #16
add r0, r0, r3, lsl #24
add r0, r0, r1, lsl #8
bx lr
clang x86
f: # @f
shl esi, 8
lea eax, [rsi + rdi]
shl edx, 16
or eax, edx
shl ecx, 24
or eax, ecx
ret
Only on very small micros I would recommend the union way
uint32_t g(uint8_t a, uint8_t b,uint8_t c,uint8_t d)
{
union
{
uint32_t v32;
uint8_t v8[4];
}x = {.v8[0] = a, .v8[1] = b, .v8[2] = c, .v8[3] = d};
return x.v32;
}
as it easier for it to optimize:
__zero_reg__ = 1
f:
push r16
push r17
ldi r25,lo8(0)
ldi r26,lo8(0)
ldi r27,hi8(0)
add r24,r22
adc r25,__zero_reg__
adc r26,__zero_reg__
adc r27,__zero_reg__
ldi r21,lo8(0)
subi r20,lo8(-(8))
sbci r21,hi8(-(8))
rjmp 2f
1: lsl r24
rol r25
rol r26
rol r27
2: dec r20
brpl 1b
ldi r19,lo8(0)
subi r18,lo8(-(16))
sbci r19,hi8(-(16))
rjmp 2f
1: lsl r24
rol r25
rol r26
rol r27
2: dec r18
brpl 1b
mov r19,r24
clr r18
clr r17
clr r16
mov r22,r16
mov r23,r17
mov r24,r18
mov r25,r19
pop r17
pop r16
ret
g:
push r16
push r17
mov r25,r18
mov r17,r22
mov r22,r24
mov r23,r17
mov r24,r20
pop r17
pop r16
ret
Upvotes: 1