C ++。不同平台上的结构填充/对齐以及布局兼容性的自动检查

时间:2015-07-11 10:41:22

标签: c++ c++11 struct embedded padding

我有嵌入式设备连接到PC 以及一些具有许多字段和自定义类型FixedPoint_t数组的大结构S. FixedPoint_t是一个模板化的POD类,只有一个数据成员,大小从char到long不等,具体取决于模板参数。无论如何,它通过了static_assert((std::is_pod<FixedPoint_t<0,8,8> >::value == true),"");

如果这个大结构在嵌入式系统和控制PC上具有兼容的内存表示,那将是一件好事。这允许将通信协议显着简化为诸如“设置具有偏移N到值V的字/字节”的命令。假设两个平台上的endianess都是相同的。

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

  1. 使用双面打包的#pragma。 但是当我将属性((压缩))添加到struct S声明时,我收到了警告 警告:由于解压缩的非POD字段而忽略压缩属性。 这是因为FixedPoint_t未声明为压缩。 我不希望将它声明为打包,因为这种类型在整个程序中被广泛使用并且打包会导致性能下降。

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

  3. 手动控制填充。我可以添加一些字段,重新排序其他字段...只是在两个平台上都没有填充。这将使我满意。但是我需要一个很好的方法来编写一个测试,告诉我填充是否存在。 我可以将sizeof()的每个字段的总和与sizeof(struct)进行比较。 我可以在两个平台上比较offsetof()每个struct字段。 两种变体都很丑陋......

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

    编辑:在两个平台上比较sizeof(big struct)来检测布局兼容性(假设endianess相等)是否足够?我认为如果填充不同,大小应该不匹配。

    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;
        }
    }
    

1 个答案:

答案 0 :(得分:1)

我强烈建议使用选项2:

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

要创建数据结构和ser / 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字段的替代,您可以使用&#34;类型&#34;代码(例如char映射到实现定义的类型。这样你可以添加相同宽度但不同编码的自定义类型(例如uint32_t和IEEE754 - float)。这将完全抽象出来来自物理机器的网络协议编码(最佳解决方案)。注意注意阻碍您使用不会使代码复杂化的常见编码(字面意思)。