简体   繁体   中英

Union struct: printing struct member of type uint32_t skips two bytes and prints wrong value

Need help with union struct. I'm receiving byte stream that consists of various packets, so I'm putting the byte data into union struct and accessing needed data via struct members. The problem is with uint32_t type member - the read skips its two bytes and shows wrong value when accessing via its member. Here's full demo code:

PacketUtils.h

#include <stdint.h>

typedef struct {

  uint8_t startSymbol;
  uint8_t packetType;
  uint32_t deviceId;
  uint16_t packetCRC;

} PacketData;

typedef union {

  uint8_t *bytes; // stores raw bytes
  PacketData *packet; 

} Packet;

// Puts bytes into predefined struct
void getPacketFromBytes(void *bytes, Packet *packetRef);

PacketUtils.c

#include <stdio.h>
#include "UnionStruct.h"

void getPacketFromBytes(void *bytes, Packet *packetRef)
{
  uint8_t *rawBytes = (uint8_t *)bytes;
  packetRef->bytes = rawBytes;
}

Calling code:

// sample byte data
uint8_t packetBytes[] = {0x11, 0x02, 0x01, 0x01, 0x01, 0x03, 0xbb, 0xbd};

Packet packetRef;
getPacketFromBytes(packetBytes, &packetRef);

printf("%x\n", packetRef.packet->startSymbol); // good - prints 0x11
printf("%x\n", packetRef.packet->packetType); // good - prints 0x02 
printf("%x\n", packetRef.packet->deviceId); // bad - prints bd bb 03 01
printf("%x\n", packetRef.packet->packetCRC); // bad - prints 36 80 (some next values in memory)

Everything is OK when PacketData struct consist of uint8_t or uint16_t type members then the print shows correct values. However, printing deviceId of type uint32_t skips two bytes (0x01 0x01) and grabs last 4 bytes. Printing packetCRC prints the values out of given byte array - some two values in memory, like packetBytes[12] and packetBytes[13]. I can't figure out why it skips two bytes...

The 32-bit number will be aligned on a 4-byte boundary only. If you move it to the start of your struct, it may just work as you want.

Processors usually are optimised to fetch data on multiples of the datum size - 4 bytes for 32-bit, 8 bytes for 64-bit... - and the compiler knows this and adds gaps into the data structures to make sure that the processor can fetch the data efficiently.

If you don't want to deal with the padding and can't move the data structure around, you could define

typedef struct {
    uint8_t startSymbol;
    uint8_t packetType;
    uint16_t deviceIdLow;
    uint16_t deviceIdHigh;
    uint16_t packetCRC;
} PacketData;

and then just write

uint32_t deviceID = packetRef.packet->deviceIdLow | (packetRef.packet->deviceIdLow << 16);

The problem is due to the fields being padded out to default alignment on your platform. On most modern architectures 32-bit values are most efficient when read/written to a 32-bit word aligned address.

In gcc you can avoid this by using a special attribute to indicate that the structure is "packed". See here:

http://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Type-Attributes.html

So struct definition would look something like this:

typedef struct {

    uint8_t startSymbol;
    uint8_t packetType;
    uint32_t deviceId;
    uint16_t packetCRC;

} PacketData __attribute__((packed));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM