如果我布置结构的字段以便它们不需要任何填充,那么符合标准的 C++ 编译器可以添加额外的东西吗?

Posted

技术标签:

【中文标题】如果我布置结构的字段以便它们不需要任何填充,那么符合标准的 C++ 编译器可以添加额外的东西吗?【英文标题】:If I lay out the fields of my struct so they shouldn't need any padding, can a conforming C++ compiler add extra anyway? 【发布时间】:2015-01-14 22:28:38 【问题描述】:

我想在 C++ 中以现有的二进制协议格式(我正在编写一个 memcached 客户端)格式化一个数据包。在 C 中,我可以这样做:

    typedef struct 
        uint8_t magic;
        uint8_t opcode;
        uint16_t keylen;
        uint8_t extlen;
        uint8_t datatype;
        uint16_t reserved;
        uint32_t bodylen;
        uint32_t opaque;
        uint64_t cas;
     request_header;

在 C++ 中,通常编译器可以在字段之间添加填充。但是,上面的结构经过精心布局,因此所有内容都可以在没有填充的情况下对齐,假设 n 位类型只需要在 n 位边界上对齐。所以在 C++ 中,根据标准,我安全吗?或者一个符合标准的 C++ 编译器是否可以添加额外的填充,从而阻碍我使用它来布置我的位?

【问题讨论】:

填充(对齐)是为了优化特定 CPU 架构的访问。所以你不能(想象如何以不同的方式进行填充以优化 16 位或 32 位地址)。 您希望该结构在 36 位计算机上如何布局? @Barmar 36 位计算机不会定义 uintXX_t 类型,所以这是一个无关紧要的问题。 @Mark Ransom 我认为您的意思是uint8_tuint16_t 等(OP 的类型)uint36_t 在 36 位机器上可能会很好。 不要使用typedef struct ... foobar;结构,尤其是在C++中,这是邪恶的 【参考方案1】:

这个不用担心,让编译器告诉你这很奇怪:

  static_assert(sizeof(request_header) == 24, "Unexpected packet size");

【讨论】:

最佳实用答案。【参考方案2】:

你是对的,C++ 可以任意填充。来自 C++.11 §9.2¶14(重点是我的):

分配具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。未指定具有不同访问控制的非静态数据成员的分配顺序 (11)。 实施对齐要求可能会导致两个相邻的成员不能紧挨着分配;管理虚拟功能 (10.3) 和虚拟基类 (10.1) 的空间要求也是如此。

C 也允许添加填充字节,所以这不是 C++ 特有的。来自 C.11 §6.7.2.1¶15(重点是我的):

在结构对象中,非位域成员和位域所在的单元的地址按声明顺序递增。一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。 结构对象中可能有未命名的填充,但不是在其开头。

如果你想避免填充,唯一的最大可移植方式是在发送时将数据结构自己打包到连续的内存中(例如,vector),并在接收时将序列化的数据解包到你的数据结构中。您的编译器可能会提供扩展以允许您将 struct 中的所有成员保持连续(例如,GCC 的 packed 属性,或 VC++ 的 pack pragma,如 here 所述)。

【讨论】:

【参考方案3】:

在 C++ 中有一个叫做 POD for plain-old-data 的东西。基本上,如果满足某些限制,C++ 中的结构就是 POD,它将与 C 代码中定义的相同结构逐字节兼容。

要成为 POD,结构必须没有访问说明符(公共、私有),也不能有非静态成员函数,包括运算符、构造函数和析构函数。

【讨论】:

标准布局与此处相关(这是 POD 的超集) 虽然它与 C 代码中定义的相同结构是逐字节兼容的,但 C 代码版本存在同样的问题:任意填充。 @MartinC.Martin 所以标准中没有说明固定宽度整数类型的对齐方式?奇怪的。现在我要重写我所有的代码,假设一个字节中的位数等于 8,必须使用 CHAR_BIT。 @NickZavaritsky:C89 的作者不想阻止 C 在具有非常奇怪的对齐要求的平台上发挥作用,并且认为没有必要说“不要愚蠢或迟钝” " 以使编译器编写者避免在预期目标上插入无用的填充。不幸的是,很多在 25 年前会被恰当地认为是迟钝的东西在今天却很流行。

以上是关于如果我布置结构的字段以便它们不需要任何填充,那么符合标准的 C++ 编译器可以添加额外的东西吗?的主要内容,如果未能解决你的问题,请参考以下文章

gcc/clang 在基本结构的后填充中布置派生结构的字段[重复]

如何获取这些数组并使用它们来填充结构的字段?

一次性填充文件中的占位符

用零填充的 varchar(5) 字段中的第一个未使用的数字

如何将打开的文件描述符导出到执行的孩子

C#关键字:访问修饰符