C ++ struct padding和字段打包

时间:2013-12-02 08:57:09

标签: c++ padding

C ++标准中是否有任何内容阻止编译器打包其字段,以便一个字段可能与另一个字段的填充重叠?

继承时允许(可能通过空基类优化)。为了说明该场景,请考虑以下代码段:

#include <cstdint>
#include <cstdlib>

struct A {
    A() { a2 = rand(); } // force non-POD
    uint32_t a1;
    uint16_t a2;
};

struct B {
    A b1;
    uint16_t b2;
};

struct C : A {
    uint16_t c;
};

BC包含相同的字段,但C只会占用8个字节,而B会在b2之前插入填充。

我用clang(Apple LLVM 5.0版)和GCC 4.7.3测试了它们,它们的行为相同,并产生以下布局:

struct A [8 Bytes]
    0: [uint32_t : 4] a1
    4: [uint16_t : 2] a2
   --- 2 Bytes padding ---                                                             

struct B [12 Bytes]
    0: [uint32_t : 4] b1.a1
    4: [uint16_t : 2] b1.a2
   --- 2 Bytes padding ---                                                             
    8: [uint16_t : 2] b2
   --- 2 Bytes padding ---                                                             

struct C [8 Bytes]
    0: [uint32_t : 4] A::a1
    4: [uint16_t : 2] A::a2
    6: [uint16_t : 2] c

(如果A成为POD,则BC都会有填充,大概是因为允许offsetof()并且优化会变得可见并且可能会破坏代码)。

我很好奇,是否有充分理由不在B案例中最佳地打包字段?

2 个答案:

答案 0 :(得分:4)

一个struct不会与另一个struct重叠,即使它重叠的struct元素是“填充”。

想象一下:

 A a;
 B x;
 memcpy(&x.b1, a, sizeof(a));

如果B已被“打包”,以便b2位于a2中的A元素旁边,那么它将被{{1}中的任何垃圾覆盖1}} - 这可能不是这些代码的原始作者所期望的(我很确定标准说上面必须工作)。

但是,您的第二个示例不是包含另一个a的{​​{1}}。它是struct继承自另一个struct。在这种情况下,原始struct A不能独立于其他struct成员使用(在本例中为struct)。该标准将有一节说明,将struct类型c复制到struct类型A继承自struct的{​​{1}}将是“未定义的行为” ,允许编译器在这种情况下“打包”。

所以这些是两个独立的案例,它们有不同的局限性。

答案 1 :(得分:1)

I'm curious, is there's a good reason for not packing the fields optimally in the B case?

在这种情况下,编译器确保类型适合4字节边界(对齐为typicallyperformance optimization)。因此,它确保sizeof(A)为8个字节。如果将B打包为仅占用8个字节,则表示sizeof(b1)必须为6,因此sizeof(b1) != sizeof(A)无效。

sizeof(b1)等于sizeof(A)的原因是sizeof运算符不计算表达式 - 它在编译时进行计算,因此它对表达式类型{{1}进行操作} b1

这意味着你最终会得到以下内容:

A

从A继承C时,它是全新的类型,因此编译器可以根据需要对齐它。