Albert Tomanek
Albert Tomanek

Reputation: 397

Copy low-order bytes of an integer whilst preserving endianness

I need to write a function that copies the specified number of low-order bytes of a given integer into an address in memory, whilst preserving their order.

void lo_bytes(uint8_t *dest, uint8_t no_bytes, uint32_t val)

I expect the usage to look like this:

uint8 dest[3];
lo_bytes(dest, 3, 0x44332211);
// Big-endian:    dest = 33 22 11
// Little-endian: dest = 11 22 33

I've tried to implement the function using bit-shifts, memcpy, and iterating over each byte of val with a for-loop, but all of my attempts failed to work on either one or the other endianness.

Is it possible to do this in a platform-independent way, or do I need to use #ifdefs and have a separate piece of code for each endianness?

Upvotes: 3

Views: 571

Answers (3)

Brendan
Brendan

Reputation: 37262

Is it possible to do this in a platform-independent way, or do I need to use #ifdefs and have a separate piece of code for each endianness?

No, that doesn't even make sense. Anything that cares about a specific characteristic of a platform (e.g. endianness) can't be platform independent.

Example 1 (platform independent):

// Copy the 3 least significant bytes to dest[]

dest[0] = value & 0xFF; dest[1] = (value >> 8) & 0xFF; dest[2] = (value >> 16) & 0xFF;

Example 2 (platform independent):

// Copy the 3 most significant bytes to dest[]

dest[0] = (value >> 8) & 0xFF; dest[1] = (value >> 16) & 0xFF; dest[2] = (value >> 24) & 0xFF;

Example 3 (platform dependent):

// I want the least significant bytes on some platforms and the most significant bytes on other platforms

#ifdef PLATFORM_TYPE_A
    dest[0] = value & 0xFF; dest[1] = (value >> 8) & 0xFF; dest[2] = (value >> 16) & 0xFF;
#endif
#ifdef PLATFORM_TYPE_B
    dest[0] = (value >> 8) & 0xFF; dest[1] = (value >> 16) & 0xFF; dest[2] = (value >> 24) & 0xFF;
#endif

Note that it makes no real difference what the cause of the platform dependence is (if it's endianness or something else), as soon as you have a platform dependence you can't have platform independence.

Upvotes: 2

John Bollinger
John Bollinger

Reputation: 181734

I've tried to implement the function using bit-shifts, memcpy, and iterating over each byte of val with a for-loop, but all of my attempts failed to work on either one or the other endianness.

All arithmetic, including bitwise arithmetic, is defined in terms of the values of the operands, not their representations. This cannot be sufficient for you because you want to obtain a result that differs depending on details of the representation style for type uint32_t.

You can operate on object representations via various approaches, but you still need to know which bytes to operate upon. That calls for some form of detection. If big-endian and little-endian are the only byte orders you're concerned with supporting, then I favor an approach similar to that given in @P__J__'s answer:

void lo_bytes(uint8_t *dest, uint8_t no_bytes, uint32_t val) {
    static const union { uint32_t i; uint8_t a[4] } ubytes = { 1 };

    memcpy(dest, &val + (1 - ubytes.a[0]) * (4 - no_bytes), no_bytes);
}

The expression (1 - ubytes.a[0]) evaluates to 1 if the representation of uint32_t is big endian, in which case the high-order bytes occur at the beginning of the representation of val. In that case, we want to skip the first 4 - no_bytes of the representation and copy the rest. If uint32_t has a little-endian representation, on the other hand, (1 - ubytes.a[0]) will evaluate to 0, with the result that the memcpy starts at the beginning of the representation. In every case, whichever bytes are copied from the representation of val, their order is maintained. That's what memcpy() does.

Upvotes: 3

0___________
0___________

Reputation: 68013

int detect_endianess(void)  //1 if little endian 0 if big endianes
{
    union
    {
        uint16_t u16;
        uint8_t u8[2];
    }val = {.u16 = 0x1122};

    return val.u8[0] == 0x22;
}

void lo_bytes(void *dest, uint8_t no_bytes, uint32_t val)
{
    if(detect_endianess())
    {
        memcpy(dest, &val, no_bytes);
    }
    else
    {
        memcpy(dest, (uint8_t *)(&val) + sizeof(val) - no_bytes, no_bytes);
    }
}

Upvotes: 2

Related Questions