初始化、构造和转换结构到字节数组导致未对齐

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(应用程序二进制接口)。