sizeof(T)是否等于sizeof(int)?

13
我一直在仔细研究这份标准草案,但似乎找不到我需要的内容。
如果我有一个标准布局类型。
struct T {
   unsigned handle;
};

然后我知道对于某些 T t;reinterpret_cast<unsigned*>(&t) == &t.handle

目标是创建一些 vector<T> v 并将 &v[0] 传递给期望指向无符号整数数组的指针的 C 函数。

因此,标准是否定义了 sizeof(T) == sizeof(unsigned) 并且是否意味着 T 数组具有与 unsigned 数组相同的布局?

虽然 this question 解决了一个非常类似的主题,但我正在询问数据成员和类都是标准布局,并且数据成员是基本类型的特定情况。

我已经阅读了一些段落,似乎暗示着这可能是真的,但没有直接回答。例如:

§9.2.17

如果两个标准布局结构(Clause 9)类型具有相同数量的非静态数据成员,且对应的非静态数据成员(按声明顺序)具有布局兼容类型,则它们是布局兼容的。


为什么不直接使用vector::data()呢? - user2249683
@anthony-arnold 抱歉,我没有仔细阅读问题。-- 您可以始终使用 static_assert 来处理可能不可移植的代码。 - dyp
4
这是一个人为制造的反例,链接在此 - dyp
我无法想象数据成员是否为标准布局会有什么影响。这个方面被封装在其容器的填充行为中。 - tenfour
6
我猜你在寻求一个根本不存在的保证。结构体可能会在末尾添加填充字节,与布局类型无关。 - tenfour
显示剩余3条评论
3个回答

6
你基本上是在询问,假设给定以下内容:
struct T {
    U handle;
};

sizeof(T)是否等于sizeof(U)?”并不保证。ISO C++03标准的第9.2/17节指出:“通过适当转换使用reinterpret_cast得到的POD结构对象的指针将指向其初始成员(如果该成员是位域,则指向其所在的单元)以及反之。”假设您有一个T结构体的数组。那么反之部分意味着T::handle中任何一个成员的地址也必须是T结构体的有效地址。现在,假设这些成员都是char类型,并且您的声明是正确的。这将意味着struct T将被允许拥有不对齐的地址,这似乎相当不可能。标准通常试图不以这种方式约束实现。为了使您的声明正确,标准必须要求struct T被允许具有不对齐的地址。而且,所有结构体都必须允许,因为struct T可以是前向声明的不透明类型。此外,第9.2/17节继续说明:“[注意:因此,在POD结构对象内可能会有未命名的填充,但在其开头不会有填充以获得适当的对齐。]”换句话说,不能保证永远没有填充。

如果T::handle的类型为char,那么alignof(T::handle)就是alignof(char) == 1。因此,对于T任何对齐方式都会导致在T数组中对齐的handles - 实际上,handle 不能不对齐。 - Casey
@jamesdlin 我理解你的观点,这确实令人失望。我希望在标准布局/整数/基本对齐等方面有某种附加条件。唉,无论如何。我已经添加了一些编译时检查,确保 sizeof(T) == sizeof(U),如果不相等,则以更长的方式进行调用。 - Anthony

3

撤回:这个论点是错误的。引理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),则剩余的字节必须是尾填充,即在Tu表示后插入的未使用的填充字节。
简而言之,论点如下:
  • 标准详细说明了实现何时可以向标准布局类添加填充,
  • 在此特定情况下,没有任何一个情况适用,因此
  • 符合规范的实现不能添加填充。

填充可能的来源

标准允许实现在什么情况下向标准布局类的表示添加这样的填充?当需要对齐时:根据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:对于任何具有默认对齐方式的类型Walignof(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),如所需。


1
请您详细说明一下3.11/1是如何要求alignof(W)必须“整除”LCM(W)的?即,为什么它不允许超过对齐要求(不是指扩展对齐,而是例如alignof(W)== 2*LCM(W))? - dyp
3.11/1中提到对齐是“...在给定对象可以分配的连续地址之间的字节数。”如果foo是可接受的,那么下一个连续地址将导致正确对齐成员的结果为foo + LCM(W)2 * LCM(W)显然是适当对齐的,但不是连续的。 - Casey
1
我认为这需要将“可以分配”解释为对实现施加的限制。如果你将其解释为对程序施加的限制,2*LCM(W)可能是可行的(即使硬件可以支持较弱的对齐方式,实现要求程序对该组合类型的对象进行“超对齐”)。 - dyp
1
@dyp 好的,我现在明白你的意思了。我假设一个聚合类型的对齐方式仅由其成员的对齐要求所决定。你建议实现可能对聚合物有比对齐成员所需更严格的要求。 - Casey
1
@dyp 在核实之后,我认同该标准允许这种解释。感谢您的审核并指出隐藏的前提条件。 - Casey
@Casey,感谢您认真思考并回答问题。很抱歉结果是错误的,这对您和我自己提出问题的努力都是不幸的。 - Anthony

3

我习惯于使用Borland环境,对于它们:

T在你的情况下是一个结构体,所以sizeof(T)是结构体的大小

  • 这取决于您编译器的#pragma packalign设置
  • 因此有时它可以大于sizeof(unsigned) !!!

出于同样的原因,如果您有4字节结构体(uint32)和16字节对齐

  • struct T { uint32 u; };
  • 那么T a[100]与uint32 a[100]不同;
  • 因为T是uint32 + 12个字节的空格!!!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接