c++.结构在不同平台上的填充/对齐和布局兼容性的自动检查



我已将嵌入式设备连接到PC以及一些大型结构体S,其中包含许多自定义类型FixedPoint_t的字段和数组。FixedPoint_t是一个模板化的POD类,只有一个数据成员,根据模板参数的不同,其大小从char到long不等。不管怎样,它通过了static_assert((std::is_pod<FixedPoint_t<0,8,8> >::value == true),"");

如果这个大结构体在嵌入式系统和控制PC上都有兼容的底层内存表示就好了。这允许显著简化通信协议的命令,如"将偏移量N的字/字节设置为值V"。假设两个平台上的尾序相同。

我在这里看到了三个解决方案:

  1. 使用类似#pragma的东西在两边打包。但是当我把属性((包装))到结构S声明时,我得到了警告警告:由于未打包的非pod字段而忽略打包属性。这是因为FixedPoint_t没有被声明为打包的。我不想将它声明为打包的,因为这种类型在整个程序中广泛使用,打包会导致性能下降。

  2. 进行正确的结构体序列化。这是不可接受的,因为代码膨胀,额外的内存使用……协议将更加复杂,因为我需要随机访问结构。现在我认为这不是一个选择。

  3. 手动控制填充。我可以添加一些字段,重新排序其他…只是为了在两个平台上实现无填充。这个我现在就满意了。但是我需要一个好的方法来写一个测试,显示我是填充是否存在。我可以比较sizeof()每个字段和sizeof(struct)的总和。我可以比较两个平台上每个结构体字段的offsetof()。

你推荐什么?我对测试中的手动填充控制和自动填充检测特别感兴趣。

EDIT:在两个平台上比较sizeof(大结构体)是否足以检测布局兼容性(假设端序相等)?我认为大小不应该匹配,如果填充会不同。

EDIT2:

//this struct should have padding on 32bit machine
//and has no padding on 8bit
typedef struct
{
    uint8_t f8;
    uint32_t f32;
    uint8_t arr[5];
} serialize_me_t;
//count of members in struct
#define SERTABLE_LEN    3 
//one table entry for each serialize_me_t data member
static const struct {
    size_t width;
    size_t offset;
//    size_t cnt;  //why we need cnt?
} ser_des_table[SERTABLE_LEN] =
    {
        { sizeof(serialize_me_t::f8), offsetof(serialize_me_t, f8)},
        { sizeof(serialize_me_t::f32), offsetof(serialize_me_t, f32)},
        { sizeof(serialize_me_t::arr), offsetof(serialize_me_t, arr)},
    };
void serialize(void* serialize_me_ptr, char* buf)
{
    const char* struct_ptr = (const char*)serialize_me_ptr;
    for(int i=0; i<SERTABLE_LEN; I++)
    {   
        struct_ptr += ser_des_table[i].offset;
        memcpy(buf, struct_ptr, ser_des_table[i].width );        
        buf += ser_des_table[i].width;
    }
}

我强烈建议使用选项2:

  • 为将来的更改(新的PCD/ABI,编译器,平台等)保存
  • 如果考虑周到,代码膨胀可以保持在最低限度。每个方向只需要一个功能。
  • 您可以自动创建所需的表/代码(半)(我使用Python)。这样双方将保持同步。
  • 无论如何,您肯定应该向数据添加CRC。由于您可能不希望在rx/tx-interrupt中计算此值,因此无论如何您都必须提供一个数组。直接使用结构体将很快成为维护的噩梦。更糟糕的是,如果其他人必须跟踪这些代码。
  • 协议等倾向于被重用。如果它是一个具有不同端序的平台,另一种方法就会失败。

要创建数据结构和server/des表,可以使用offsetof来获取结构中每种类型的偏移量。如果这个表是一个包含文件,那么它可以在两边使用。您甚至可以创建结构和表,例如通过Python脚本。将它添加到构建过程中可以确保它始终是最新的,并且可以避免额外的输入。

例如(在C语言中,只是为了理解):

// protocol.inc
typedef struct {
    uint32_t i;
    uint 16_t s[5];
    uint32_t j;
} ProtocolType;
static const struct {
    size_t width;
    size_t offset;
    size_t cnt;
} ser_des_table[] = {
    { sizeof(ProtocolType.i), offsetof(ProtocolType.i), 1 },
    { sizeof(ProtocolType.s[0]), offsetof(ProtocolType.s), 5 },
    ...
};

如果不是自动创建的,我会使用宏来生成数据。可能通过包含该文件两次:一次生成结构定义,另一次生成表。这可以通过重新定义中间的宏来实现。

您应该关心有符号整数和浮点数的表示(实现定义的,浮点数可能是标准建议的IEEE754)。

作为width字段的替代,您可以使用"类型"代码(例如,映射到实现定义的类型的char)。这样你可以添加自定义类型相同的宽度,但不同的编码(例如uint32_t和IEEE754- float)。这将完全从物理机器中抽象出网络协议编码(最好的解决方案)。注意,注意会妨碍您使用不会使单个位(字面上)的代码复杂化的通用编码。

最新更新