I am using IAR (a C compiler) to program for TI chips (16 bits MCU).
I have the following strcture,
//I use union mainly because sometimes I use the 2 bytes word value
// and sometimes I only use one byte (either a or b)
typedef union {
uint16_t address;
struct
{
uint8_t parta;
uint8_t partb;
} details;
} address_t;
Then I have the following mac address definition,
typedef struct
{
uint8_t frame_type;
uint8_t sequence_number;
address_t source_address;
} mac_header_t;
so far so good.
When I receive a packet over radio, it's stored in a buffer array.
uint8_t buffer[MAX_PACKET_LEN];
//the first byte is packet length, mac address follows
mac_header_t *header = (mac_header_t *)(buffer + 1);
The the weird things happens,
//The packet is say
// 0x07 (length)
// 0x07 (frame_type)
// 0x04 (sequence_number)
// 0x00 (source address parta)
// 0x00 (source address partb)
//The source address is indeed 0x00 0x00 (2 bytes)
assert(header->source_address.details.parta == 0); //correct! there's no problem
assert(header->source_address.details.partb == 0); //correct! there's no problem
//assignment from header->source_address to another object
address_t source_address = header->source_address;
assert(source_address.details.parta == 0); //no! it's 0x04!
assert(source_address.details.partb == 0); //this is right
So the weird thing is, after assignment from header->source_address, to another object, the alignment changed from 0x00 0x00 to 0x04 0x00 (note the buffer, this actually moves the pointer 1 byte forward)!
After I used #pragma pack(1), things are solved.
However, I am not sure why this actually caused problem. Assignment of 2 object at different alignment boundary will result in two completely different values? (right hand side is 0x00 0x00, and left hand side is 0x04 0x00)
Is this code undefined in C? Or it's a bug of IAR?
Thanks.
You cannot use C structs/unions for storing data protocols or create exact memory maps.
This is because a C compiler may insert padding bytes anywhere inside a struct/union except at the very beginning. How this is done, or what values the padding bytes get, is implementation-defined behavior.
That is what's causing your problems. When you attempt to "alias" the raw data buffer to correspond to your structs, you invoke undefined behavior, because the struct memory mapping does not correspond to the raw data.
There are some ways to solve this issue:
Use structs/unions in a safe, deterministic manner. You always need to use a static assert, to ensure that your struct does not contain padding. You can write such an assert as:
static_assert (sizeof(my_struct) == (sizeof(my_struct.member1) +
sizeof(my_struct.member2) +
...),
"Padding detected!");
Once you have this is place, you have prevented the bug from happening. But to actually solve the issue, you'll have to remove the padding in some compiler-specific way, such as the #pragma pack(1)
.
If your compiler has no way of removing the padding, you have to write serialization/de-serialization functions, as suggested in a comment. It is essentially just a data-shovelling function like this:
void mac_serialize (mac_header_t* dest, const uint8_t* source)
{
dest->details.parta = source[BYTE_PARTA];
dest->details.partb = source[BYTE_PARTB];
...
}
Also please note that the way you have created the address union, it is endianess dependant. This may also be another issue, unrelated to padding.