初始化、构造和转换结构到字节数组导致未对齐
Initializing, constructing and converting struct to byte array causes misalignment
我正在尝试设计一个用于字节级通信的数据结构(我已经缩短了保存 space 的时间,但我想你明白了):
/* PACKET.H */
#define CM_HEADER_SIZE 3
#define CM_DATA_SIZE 16
#define CM_FOOTER_SIZE 3
#define CM_PACKET_SIZE (CM_HEADER_SIZE + CM_DATA_SIZE + CM_FOOTER_SIZE)
// + some other definitions
typedef struct cm_header{
uint8_t PacketStart; //Start Indicator 0x5B [
uint8_t DeviceId; //ID Of the device which is sending
uint8_t PacketType;
} CM_Header;
typedef struct cm_footer {
uint16_t DataCrc; //CRC of the 'Data' part of CM_Packet
uint8_t PacketEnd; //should be 0X5D or ]
} CM_Footer;
//Here I am trying to conver a few u8[4] tp u32 (4*u32 = 16 byte, hence data size)
typedef struct cm_data {
union {
struct{
uint8_t Value_0_0:2;
uint8_t Value_0_1:2;
uint8_t Value_0_2:2;
uint8_t Value_0_3:2;
};
uint32_t Value_0;
};
//same thing for Value_1, 2 and 3
} CM_Data;
typedef struct cm_packet {
CM_Header Header;
CM_Data Data;
CM_Footer Footer;
} CM_Packet;
typedef struct cm_inittypedef{
uint8_t DeviceId;
CM_Packet Packet;
} CM_InitTypeDef;
typedef struct cm_appendresult{
uint8_t Result;
uint8_t Reason;
} CM_AppendResult;
extern CM_InitTypeDef cmHandler;
这里的目标是为通过 USB 接口传输数据建立可靠的结构。最后,CM_Packet
应转换为 uint8_t
数组并提供给单片机(stm32)的数据传输寄存器。
在 main.c
文件中,我尝试初始化结构以及与此数据包相关的其他一些内容:
/* MAIN.C */
uint8_t packet[CM_PACKET_SIZE];
int main(void) {
//use the extern defined in packet.h to init the struct
cmHandler.DeviceId = 0x01; //assign device id
CM_Init(&cmHandler); //construct the handler
//rest of stuff
while(1) {
CM_GetPacket(&cmHandler, (uint8_t*)packet);
CDC_Transmit_FS(&packet, CM_PACKET_SIZE);
}
}
这里是 packet.h
的实现,它把一切都搞砸了。我添加了 packet[CM_PACKET_SIZE]
来观看,但它就像是随机生成的。有时纯粹是运气好,我可以在这个数组中看到一些我感兴趣的值!但这大约是 1% 的时间!
/* PACKET.C */
CM_InitTypeDef cmHandler;
void CM_Init(CM_InitTypeDef *cm_initer) {
cmHandler.DeviceId = cm_initer->DeviceId;
static CM_Packet cmPacket;
cmPacket.Header.DeviceId = cm_initer->DeviceId;
cmPacket.Header.PacketStart = CM_START;
cmPacket.Footer.PacketEnd = CM_END;
cm_initer->Packet = cmPacket;
}
CM_AppendResult CM_AppendData(CM_InitTypeDef *handler, uint8_t identifier,
uint8_t *data){
CM_AppendResult result;
switch(identifier){
case CM_VALUE_0:
handler->Packet.Data.Value_0_0 = data[0];
handler->Packet.Data.Value_0_1 = data[1];
handler->Packet.Data.Value_0_2 = data[2];
handler->Packet.Data.Value_0_3 = data[3];
break;
//Also cases for CM_VALUE_0, 1 , 2
//to build up the CM_Data sturct of CM_Packet
default:
result.Result = CM_APPEND_FAILURE;
result.Reason = CM_APPEND_CASE_ERROR;
return result;
break;
}
result.Result = CM_APPEND_SUCCESS;
result.Reason = 0x00;
return result;
}
void CM_GetPacket(CM_InitTypeDef *handler, uint8_t *packet){
//copy the whole struct in the given buffer and later send it to USB host
memcpy(packet, &handler->Packet, sizeof(CM_PACKET_SIZE));
}
所以,问题是这段代码在 99% 的时间里给我随机的东西。它从来没有 CM_START
这是数据包的起始指示符到我想要的值。但大多数时候它的 CM_END
字节是正确的!我真的很困惑,无法找出原因。在难以调试的嵌入式平台上工作我有点迷路了...
如果您将数据传输到另一个(不同的)体系结构,请不要只将结构作为 blob 传递。这就是通往地狱的道路:字节序、对齐、填充字节等。所有这些都可能(并且很可能会)造成麻烦。
最好以一致的方式序列化结构,可能使用一些解释的控制流,这样您就不必手动写出每个字段。 (但仍然使用标准函数来生成该流)。
一些 潜在或可能出现问题的区域:
CM_Footer
:第二个字段很可能从 32 位或 64 位边界开始,因此前面的字段将跟在填充之后。此外,该结构的末尾很可能在 32 位架构上被填充至少 1 个字节,以便在数组中使用时允许正确对齐(如果您确实需要它,编译器不会关心您)。它甚至可能是 8 字节对齐的。
CM_Header
:在这里您可能(不保证)得到一个 uint8_t 4*2 位,排序未标准化。该字段后面可能有 3 个未使用的字节,这些字节是 uint32_t 联合解释所必需的。
- 如何保证主机和目标的字节顺序相同(>uint8_t:高字节在前还是低字节在前?)?
- 一般来说,structs/unions 主机和目标的布局不必相同。即使使用相同的编译器,它们的 ABI 也可能不同,等等。即使相同 CPU,也可能存在其他系统约束。此外,对于某些 CPU,存在不同的 ABI(应用程序二进制接口)。
我正在尝试设计一个用于字节级通信的数据结构(我已经缩短了保存 space 的时间,但我想你明白了):
/* PACKET.H */
#define CM_HEADER_SIZE 3
#define CM_DATA_SIZE 16
#define CM_FOOTER_SIZE 3
#define CM_PACKET_SIZE (CM_HEADER_SIZE + CM_DATA_SIZE + CM_FOOTER_SIZE)
// + some other definitions
typedef struct cm_header{
uint8_t PacketStart; //Start Indicator 0x5B [
uint8_t DeviceId; //ID Of the device which is sending
uint8_t PacketType;
} CM_Header;
typedef struct cm_footer {
uint16_t DataCrc; //CRC of the 'Data' part of CM_Packet
uint8_t PacketEnd; //should be 0X5D or ]
} CM_Footer;
//Here I am trying to conver a few u8[4] tp u32 (4*u32 = 16 byte, hence data size)
typedef struct cm_data {
union {
struct{
uint8_t Value_0_0:2;
uint8_t Value_0_1:2;
uint8_t Value_0_2:2;
uint8_t Value_0_3:2;
};
uint32_t Value_0;
};
//same thing for Value_1, 2 and 3
} CM_Data;
typedef struct cm_packet {
CM_Header Header;
CM_Data Data;
CM_Footer Footer;
} CM_Packet;
typedef struct cm_inittypedef{
uint8_t DeviceId;
CM_Packet Packet;
} CM_InitTypeDef;
typedef struct cm_appendresult{
uint8_t Result;
uint8_t Reason;
} CM_AppendResult;
extern CM_InitTypeDef cmHandler;
这里的目标是为通过 USB 接口传输数据建立可靠的结构。最后,CM_Packet
应转换为 uint8_t
数组并提供给单片机(stm32)的数据传输寄存器。
在 main.c
文件中,我尝试初始化结构以及与此数据包相关的其他一些内容:
/* MAIN.C */
uint8_t packet[CM_PACKET_SIZE];
int main(void) {
//use the extern defined in packet.h to init the struct
cmHandler.DeviceId = 0x01; //assign device id
CM_Init(&cmHandler); //construct the handler
//rest of stuff
while(1) {
CM_GetPacket(&cmHandler, (uint8_t*)packet);
CDC_Transmit_FS(&packet, CM_PACKET_SIZE);
}
}
这里是 packet.h
的实现,它把一切都搞砸了。我添加了 packet[CM_PACKET_SIZE]
来观看,但它就像是随机生成的。有时纯粹是运气好,我可以在这个数组中看到一些我感兴趣的值!但这大约是 1% 的时间!
/* PACKET.C */
CM_InitTypeDef cmHandler;
void CM_Init(CM_InitTypeDef *cm_initer) {
cmHandler.DeviceId = cm_initer->DeviceId;
static CM_Packet cmPacket;
cmPacket.Header.DeviceId = cm_initer->DeviceId;
cmPacket.Header.PacketStart = CM_START;
cmPacket.Footer.PacketEnd = CM_END;
cm_initer->Packet = cmPacket;
}
CM_AppendResult CM_AppendData(CM_InitTypeDef *handler, uint8_t identifier,
uint8_t *data){
CM_AppendResult result;
switch(identifier){
case CM_VALUE_0:
handler->Packet.Data.Value_0_0 = data[0];
handler->Packet.Data.Value_0_1 = data[1];
handler->Packet.Data.Value_0_2 = data[2];
handler->Packet.Data.Value_0_3 = data[3];
break;
//Also cases for CM_VALUE_0, 1 , 2
//to build up the CM_Data sturct of CM_Packet
default:
result.Result = CM_APPEND_FAILURE;
result.Reason = CM_APPEND_CASE_ERROR;
return result;
break;
}
result.Result = CM_APPEND_SUCCESS;
result.Reason = 0x00;
return result;
}
void CM_GetPacket(CM_InitTypeDef *handler, uint8_t *packet){
//copy the whole struct in the given buffer and later send it to USB host
memcpy(packet, &handler->Packet, sizeof(CM_PACKET_SIZE));
}
所以,问题是这段代码在 99% 的时间里给我随机的东西。它从来没有 CM_START
这是数据包的起始指示符到我想要的值。但大多数时候它的 CM_END
字节是正确的!我真的很困惑,无法找出原因。在难以调试的嵌入式平台上工作我有点迷路了...
如果您将数据传输到另一个(不同的)体系结构,请不要只将结构作为 blob 传递。这就是通往地狱的道路:字节序、对齐、填充字节等。所有这些都可能(并且很可能会)造成麻烦。
最好以一致的方式序列化结构,可能使用一些解释的控制流,这样您就不必手动写出每个字段。 (但仍然使用标准函数来生成该流)。
一些 潜在或可能出现问题的区域:
CM_Footer
:第二个字段很可能从 32 位或 64 位边界开始,因此前面的字段将跟在填充之后。此外,该结构的末尾很可能在 32 位架构上被填充至少 1 个字节,以便在数组中使用时允许正确对齐(如果您确实需要它,编译器不会关心您)。它甚至可能是 8 字节对齐的。CM_Header
:在这里您可能(不保证)得到一个 uint8_t 4*2 位,排序未标准化。该字段后面可能有 3 个未使用的字节,这些字节是 uint32_t 联合解释所必需的。- 如何保证主机和目标的字节顺序相同(>uint8_t:高字节在前还是低字节在前?)?
- 一般来说,structs/unions 主机和目标的布局不必相同。即使使用相同的编译器,它们的 ABI 也可能不同,等等。即使相同 CPU,也可能存在其他系统约束。此外,对于某些 CPU,存在不同的 ABI(应用程序二进制接口)。