【问题标题】:C++. Struct padding / alignment on different platforms and atomatic check of layout compatibilityC++。不同平台上的结构填充/对齐和布局兼容性的原子检查
【发布时间】:2015-09-30 02:52:28
【问题描述】:

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

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

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

  1. 使用像 #pragma 这样的东西在两边都打包。 但是当我将 attribute((packed)) 放入 struct S 声明时收到警告 警告:由于未打包的非 POD 字段而忽略打包属性。 这是因为 FixedPoint_t 未声明为 packed。 我不想将它声明为打包,因为这种类型在整个程序中被广泛使用,打包会导致性能下降。

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

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

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

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

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

【问题讨论】:

  • 请不要编辑/扩展您的问题。这不是讨论论坛。如果还有其他问题,请提出新问题。但首先,尝试自己,对你得到的答案的各个方面进行一些研究。 (memcpy 不是序列化的正确方法。有关我在另一个答案中提供的示例,请参阅here,以了解正确序列化的想法。我的方法不关心填充。还要注意@ 987654325@ 及其设置实际上可能需要比移位更长的时间(这是高度可优化的)。
  • 似乎这是序列化和协议 C++ 实现的一个很好的例子。 youtu.be/wbZdZKpUVeg

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


【解决方案1】:

我强烈建议使用选项 2:

  • 您可以随时进行更改(新的 PCD/ABI、编译器、平台等)
  • 如果经过深思熟虑,可以将代码膨胀控制在最低限度。每个方向只需要一个函数。
  • 您可以(半)自动创建所需的表/代码(我使用 Python)。这样双方将保持同步。
  • 无论如何,您绝对应该为数据添加 CRC。由于您可能不想在 rx/tx 中断中计算此值,因此无论如何您都必须提供一个数组。
  • 直接使用结构将很快成为维护的噩梦。如果其他人必须跟踪此代码,那就更糟了。
  • 协议等倾向于重复使用。如果它是一个具有不同字节顺序的平台,那么另一种方法就很适用了。

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

【讨论】:

  • 对不起,你能提供更完整的使用 ser_des_table 的例子吗?我想还不明白整个想法......
  • 我不明白你错过了什么。对于每个接收/传输的数据包,您只需遍历表并将数据包数据从缓冲区(反)序列化到结构。
  • 在我原来的问题中添加了代码。我明白你的想法了吗?我只有一个问题,为什么我们需要 cnt in table?
  • @sigmaN: cnt 对于第二个字段是必需的,因为那是一个数组。抱歉,我真的也应该补充一下(现在完成,请参阅编辑)。如果你没有数组,你当然不需要那个字段。如果你想节省内存,你也可以使用比size_t更小的类型,如果你确定值不会太大(一个好主意可能是widthcnt每个都有uint16_t或甚至unsigned char - 见width的文字)
猜你喜欢
  • 1970-01-01
  • 2012-09-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-04
  • 2020-07-26
  • 2016-04-10
  • 2016-09-18
  • 2021-05-28
相关资源
最近更新 更多