撤回:论点是错误的。引理 2 的证明依赖于一个隐藏的前提,即聚合类型的对齐方式严格取决于其成员类型的对齐方式。正如Dyp points out in the commentary,标准不支持该前提。因此,struct { Foo f } 可以接受比Foo 更严格的对齐要求。
我将在这里扮演魔鬼的拥护者,因为似乎没有其他人愿意。我将争辩说,标准 C++ (
I'll refer to N3797 herein) 保证
sizeof(T) == sizeof(U) 当
T 是标准布局类 (9/7) 时,默认对齐具有单个默认对齐的非静态数据成员
U,例如,
struct T { // or class, or union
U u;
};
这是公认的:
sizeof(T) >= sizeof(U)
-
offsetof(T, u) == 0 (9.2/19)
-
U 必须是标准布局类型,T 才能成为标准布局类
-
u 具有完全由 sizeof(U) 连续内存字节组成的表示 (1.8/5)
这些事实表明T 的表示的第一个sizeof(U) 字节被u 的表示占据。如果sizeof(T) > sizeof(U),则多余的字节必须是tail padding:在T 中u 的表示之后插入未使用的填充字节。
简而言之,论据是:
- 该标准详细说明了实现可以向标准布局类添加填充的情况,
- 这些情况均不适用于此特定情况,因此
- 符合标准的实现可能不会添加填充。
填充的潜在来源
在什么情况下,标准允许实现将这种填充添加到标准布局类的表示中?当需要对齐时:根据 3.11/1,“alignment 是实现定义的整数值,表示可以分配给定对象的连续地址之间的字节数。”有两次提到添加填充,都是出于对齐原因:
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 个元素恰好是 sizeof(W) 的 n 倍(即,数组元素之间没有“外部”填充)。然后,连续数组元素的地址相隔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)。任何整数字节 n < 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)。