use*_*498 2 c embedded memory-alignment iar
我正在使用IAR(C编译器)来编程TI芯片(16位MCU).
我有以下结构,
//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;
Run Code Online (Sandbox Code Playgroud)
然后我有以下mac地址定义,
typedef struct
{
uint8_t frame_type;
uint8_t sequence_number;
address_t source_address;
} mac_header_t;
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.
当我通过无线电接收数据包时,它存储在缓冲区数组中.
uint8_t buffer[MAX_PACKET_LEN];
//the first byte is packet length, mac address follows
mac_header_t *header = (mac_header_t *)(buffer + 1);
Run Code Online (Sandbox Code Playgroud)
奇怪的事情发生了,
//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
Run Code Online (Sandbox Code Playgroud)
所以奇怪的是,在从header-> source_address赋值到另一个对象之后,对齐从0x00 0x00变为0x04 0x00(注意缓冲区,这实际上将指针向前移动1个字节)!
在我使用#pragma pack(1)后,事情就解决了.
但是,我不确定为什么这实际上会引起问题.在不同的对齐边界处分配2个对象将导致两个完全不同的值?(右侧是0x00 0x00,左侧是0x04 0x00)
这个代码在C中是否未定义?或者这是IAR的错误?
谢谢.
您不能使用C结构/联合来存储数据协议或创建精确的内存映射.
这是因为C编译器可以在结构/联合内的任何位置插入填充字节,除非在最开始.如何完成此操作或填充字节获得的值是实现定义的行为.
这就是造成你问题的原因.当您尝试对原始数据缓冲区"别名"以对应于结构时,您将调用未定义的行为,因为结构内存映射与原始数据不对应.
有一些方法可以解决这个问题:
以安全,确定的方式使用结构/联合.您始终需要使用静态断言,以确保您的结构不包含填充.你可以写这样一个断言:
static_assert (sizeof(my_struct) == (sizeof(my_struct.member1) +
sizeof(my_struct.member2) +
...),
"Padding detected!");
Run Code Online (Sandbox Code Playgroud)
一旦你有了这个地方,你就防止了这个错误的发生.但要实际解决问题,您必须以某些特定于编译器的方式删除填充,例如#pragma pack(1).
如果编译器无法删除填充,则必须按照注释中的建议编写序列化/反序列化函数.它本质上只是一个数据挖掘功能,如下所示:
void mac_serialize (mac_header_t* dest, const uint8_t* source)
{
dest->details.parta = source[BYTE_PARTA];
dest->details.partb = source[BYTE_PARTB];
...
}
Run Code Online (Sandbox Code Playgroud)
另请注意您创建地址联合的方式,它取决于endianess.这也可能是另一个与填充无关的问题.