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;
};
B
和C
包含相同的字段,但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,则B
和C
都会有填充,大概是因为允许offsetof()
并且优化会变得可见并且可能会破坏代码)。
我很好奇,是否有充分理由不在B
案例中最佳地打包字段?
答案 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字节边界(对齐为typically和performance optimization)。因此,它确保sizeof(A)
为8个字节。如果将B
打包为仅占用8个字节,则表示sizeof(b1)
必须为6,因此sizeof(b1) != sizeof(A)
无效。
sizeof(b1)
等于sizeof(A)
的原因是sizeof
运算符不计算表达式 - 它在编译时进行计算,因此它对表达式类型{{1}进行操作} b1
。
这意味着你最终会得到以下内容:
A
从A继承C时,它是全新的类型,因此编译器可以根据需要对齐它。