Home > front end >  Union fails to decode byte array
Union fails to decode byte array

Time:02-26

The following code fails to decode the latitude and longitude. The output is: 132, 1, 1645780273, 77276230, 0, -0.000000, 0.000000, doing a wrong conversion on the last 2 values latitude & longitude. If I remove the first 4 values and delete uint32_t identifiers; from the struct i get the correct result 132, 1, 1645780273, 77276230, 0, 59.877871, 10.465200

Any help in understanding this behavior ist very appreciated.

#include <stdio.h>
#include <string.h>
int main() {
    union  {
        unsigned char bytes[36];
        struct{
            uint32_t identifier;
            uint16_t telegram_size;
            uint16_t telegram_version;
            uint32_t seconds;
            uint32_t nanoseconds;
            uint32_t stat;
            double latitude;
            double longitude;
      }unpack;
    }telegram;

    unsigned char values[] = {0x23, 0x4B, 0x4D, 0x42, 0x84, 0x00, 0x01, 0x00, 0x31, 0x9d, 0x18, 0x62, 0x46, 0x24, 0x9b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9d, 0x3e, 0x2c, 0x11, 0x5e, 0xf0, 0x4d, 0x40, 0xCA, 0x32, 0xC4, 0xB1, 0x2E, 0xEE, 0x24, 0x40};
    memcpy(telegram.bytes, values, 36);

    for (int i=0;i<36;i  ){
        printf("X ", telegram.bytes[i]);
    }
    printf("\n%u, %u, %u, %u, %u, %f, %f\n", telegram.unpack.telegram_size, telegram.unpack.telegram_version, telegram.unpack.seconds, telegram.unpack.nanoseconds, telegram.unpack.stat, telegram.unpack.latitude, telegram.unpack.longitude);
    return 0;
}

CodePudding user response:

This has to do with struct padding.

Generally speaking, members of a struct start at an offset which is a multiple of that member's size for alignment purposes. Because of this, there are 4 padding bytes between the stat and latitude members so that the latter can reside at an offset that is a multiple of 8. That means the struct is 4 bytes larger than you think it is.

If you're able to, you should rearrange the members so that they align at a natural offset so that there is no padding between the members (although there may still be padding at the end).

If that's not an option, you can pack the struct using a compiler-specific attribute. If you're using gcc, you would do the following:

    struct __attribute__((__packed__)) {
        uint32_t identifier;
        uint16_t telegram_size;
        uint16_t telegram_version;
        uint32_t seconds;
        uint32_t nanoseconds;
        uint32_t stat;
        double latitude;
        double longitude;
  }unpack;

Note also that on some architectures, an unaligned read or write can cause a crash.

CodePudding user response:

You are assuming that the structure is laid out with all members contiguous. That is not a safe assumption. Implementations are free to insert arbitrary padding into structure layouts between members and after the last member. In practice, they will do so in a manner that allows them to ensure that all members can be properly aligned in memory.

In your particular case, your 8-byte doubles want to be aligned on 8-byte boundaries. Your implementation is ensuring that they can be by putting them at offsets from the beginning of the structure that are multiples of 8 bytes. That involves inserting four bytes of otherwise unused padding between stat and latitude, which your initialization does not account for. The overall structure will also have an 8-byte alignment requirement, and its total size will be a multiple of 8 bytes.

Removing the four-byte initial member allows the two doubles to be aligned without any padding, which is why that variation on your program worked as you expected.

  •  Tags:  
  • c
  • Related