Ben
Ben

Reputation: 2133

What's a compiler friendly and endian-agnostic way to write this?

I have some code on an embedded system. The code can be seen here link, particularly this snippet:

uint32_t raw_fusebits[2];
....
/* Read the fuse settings in the user row, 64 bit */
((uint16_t*)&raw_fusebits)[0] = (uint16_t)NVM_MEMORY[NVMCTRL_USER / 2];
((uint16_t*)&raw_fusebits)[1] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 1];
((uint16_t*)&raw_fusebits)[2] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 2];
((uint16_t*)&raw_fusebits)[3] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 3];

This causes a compiler warning under C99 related to strict-aliasing. Is there a better way to write this? At first I thought (using some simpler variable names for readability):

uint32_t x[2];
uint16_t a, b, c, d;
x[0] = ((uint32_t)a << 16) | ((uint32_t)b);
x[1] = ((uint32_t)c << 16) | ((uint32_t)d);

And then I switched to,

x[0] = ((uint32_t)b << 16) | ((uint32_t)a);
x[1] = ((uint32_t)d << 16) | ((uint32_t)c);

Which is correct but on my little-endian machine. And then I confused myself by wondering why endianness didn't apply when I tested these with certain values.

Is there a compiler-friendly way to rewrite the library code to be endian-agnostic?

edit: On line 73, you can see the definition for NVM_MEMORY, so the size is consistent there at least.

#define NVM_MEMORY        ((volatile uint16_t *)FLASH_ADDR)

Upvotes: 1

Views: 190

Answers (2)

user4815162342
user4815162342

Reputation: 154916

In this case you can just use memcpy as suggested by John. But if the values you need to assign are calculated, memcpy won't be sufficient. In that case, a compiler-friendly way to alias values supported by C is using a union:

union {
  struct {
    uint32_t val[2];
  } u32;
  struct {
    uint16_t val[4];
  } u16;
} raw;
...
raw.u16.val[0] = NVM_MEMORY[NVMCTRL_USER / 2];
raw.u16.val[1] = NVM_MEMORY[(NVMCTRL_USER / 2) + 1];
raw.u16.val[2] = NVM_MEMORY[(NVMCTRL_USER / 2) + 2];
raw.u16.val[3] = NVM_MEMORY[(NVMCTRL_USER / 2) + 3];

/* obtain 32-bit values from raw.u32.val[0] and raw.u32.val[1] */

Upvotes: 1

John Zwinck
John Zwinck

Reputation: 249153

How about this?

memcpy(raw_fusebits, NVM_MEMORY + (NVMCTRL_USER / 2), sizeof(raw_fusebits));

Given that the fields are copied as-is, 16 bits at a time, it doesn't seem like there's any endianness handling in the original code. And using memcpy() avoids the strict-aliasing problem.

Upvotes: 1

Related Questions