rtheunissen
rtheunissen

Reputation: 7435

Use a uint32_t to store four separate uint8_t values

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

Answers (2)

Patrick Roberts
Patrick Roberts

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

0___________
0___________

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

Related Questions