撤回:这个论点是错误的。引理2的证明依赖于一个隐藏前提,即聚合类型的对齐方式严格由其成员类型的对齐方式决定。正如Dyp在评论中指出的,该前提在标准中没有得到支持。因此,struct { Foo f }
可以具有比Foo
更严格的对齐要求。
我将扮演恶魔的代言人,因为似乎没有其他人愿意。我会辩称,标准C++(
以下简称N3797)保证当
T
是标准布局类(9/7)并具有单个默认对齐的非静态数据成员
U
时,
sizeof(T) == sizeof(U)
,例如:
struct T { // or class, or union
U u;
};
已经明确的是:
sizeof(T) >= sizeof(U)
offsetof(T, u) == 0
(9.2/19)
T
必须是标准布局类型,U
必须是T
的标准布局类型
u
的表示由恰好包含sizeof(U)
个连续字节的内存组成 (1.8/5)
这些事实意味着
T
的表示的前
sizeof(U)
个字节被
u
的表示占据。如果
sizeof(T) > sizeof(U)
,则剩余的字节必须是尾填充,即在
T
中
u
表示后插入的未使用的填充字节。
简而言之,论点如下:
- 标准详细说明了实现何时可以向标准布局类添加填充,
- 在此特定情况下,没有任何一个情况适用,因此
- 符合规范的实现不能添加填充。
填充可能的来源
标准允许实现在什么情况下向标准布局类的表示添加这样的填充?当需要对齐时:根据3.11/1,“
对齐是表示给定对象可以被分配的连续地址之间的字节数的实现定义整数值。”有两个提到添加填充的地方,都是为了对齐的原因。
5.3.3 Sizeof [expr.sizeof]/2指出:“在引用或引用类型中应用时,结果是所引用类型的大小。应用于类时,结果是该类对象中字节的总数,包括将该类型的对象放置在数组中所需的任何填充。最派生类的大小必须大于零(1.8)。应用sizeof
到基类子对象的结果是基类类型的大小。77应用于数组时,结果是数组中所有字节的总数。这意味着n个元素的数组的大小是n乘以每个元素的大小。”
9.2类成员[class.mem]/13指出:“实现对齐要求可能导致两个相邻成员之间不会立即分配;同样,虚函数(10.3)和虚基类(10.1)管理空间的要求也可能造成这种情况。
(值得注意的是,C++标准没有像C标准那样包含允许实现在结构体中插入填充的总体声明,例如,N1570(C11-ish)§6.7.2.1/15“一个结构对象中可能有匿名填充物,但不能在其开头。”和/17“结构体或联合体的末尾可能有未命名的填充物。”)
显然,9.2的文字不适用于我们的问题,因为(a)T只有一个成员,因此没有“相邻的成员”,(b)T是标准布局,因此没有虚函数或虚基类(按照9/7)。展示5.3.3/2不允许在我们的问题中填充更具挑战性。
一些前提条件
引理1:对于任何具有默认对齐方式的类型W
,alignof(W)
被sizeof(W)
除尽:根据5.3.3/2,类型W
的n个元素的数组的大小恰好是n乘以sizeof(W)
(即,在数组元素之间没有“外部”填充物)。然后,连续数组元素的地址相差sizeof(W)
个字节。由于对齐的定义,必须是alignof(W)
除以sizeof(W)
。
引理2: 具有默认对齐数据成员的默认对齐标准布局类W
的对齐alignof(W)
是数据成员对齐的最小公倍数LCM(W)
(如果没有数据成员,则为1): 给定可以分配
W
对象的地址,
LCM(W)
字节之后的地址也必须适当地对齐: 成员子对象地址之间的差异也将是
LCM(W)
字节,并且每个这样的成员子对象的对齐方式都能被
LCM(W)
整除。按照3.11/1中对齐的定义,我们有
alignof(W)
整除
LCM(W)
。任何小于
LCM(W)
的整数字节数必须不能被
W
的某些成员
v
的对齐方式整除,因此距离一个可以分配
W
对象的地址仅为
n
字节的地址因此不适合用作
W
对象的地址,即
alignof(W) >= LCM(W)
。由于
alignof(W)
整除
LCM(W)
且
alignof(W) >= LCM(W)
,我们有
alignof(W) == LCM(W)
。
结论
将此引理应用于原始问题,立即得出alignof(T) == alignof(U)
的结果,因此需要多少填充以“放置该类型的对象在数组中”?没有。由于第二个引理得到alignof(T) == alignof(U)
,并且由第一个引理知alignof(U)
整除sizeof(U)
,则必须是alignof(T)
整除sizeof(U)
,因此不需要填充字节将对象T
放置在数组中。
由于所有可能产生的填充字节都已被排除,实现不能添加填充到T
,我们有sizeof(T) == sizeof(U)
,如所需。
static_assert
来处理可能不可移植的代码。 - dyp