Question

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.

Était-ce utile?

La solution

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.

Autres conseils

Many microcontrollers have alignment requirements for multi-byte values. For example, the MSP430 family requires that 2-byte Words must be aligned on even addresses (with the low byte at the even address followed by the high byte at the next odd address). When the microcontroller tries to access a multi-byte value from a mis-aligned address then you will get undefined behavior or maybe a data abort.

The compiler makers know this and they designed the compiler to insert padding bytes into the structures you declare in order to keep each member properly aligned. When you use compiler directive to pack the structure you're telling the compiler to NOT insert padding bytes. But that doesn't remove the microcontroller's alignment restrictions. You're still going to have a problem if you access mis-aligned structure members.

When you receive a serialized message into a buffer, where pad bytes were likely removed during transmission, and the data is likely preceded by various header bytes, there is a good chance that multi-byte values in the data are not properly aligned. (The multi-byte values may not even be the correct endianness.) That is why it is good practice to manually de-serialize the message by manually copying each byte into an unpacked structure (with the proper endianness).

In your case, I'm guessing that buffer starts at an even address. Then you assign header to point to an odd address (buffer + 1). Which means the address field of address_t will fall on an odd address. And since address is a multi-byte value, you're getting undefined behavior when it is accessed. (I'm not sure why packing solved this in your case, so I may have guessed wrong, but this should give you the general idea.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top