POD 结构或标准布局类型的成员是不是保证根据其对齐要求对齐?

Posted

技术标签:

【中文标题】POD 结构或标准布局类型的成员是不是保证根据其对齐要求对齐?【英文标题】:Are members of a POD-struct or standard layout type guaranteed to be aligned according to their alignment requirements?POD 结构或标准布局类型的成员是否保证根据其对齐要求对齐? 【发布时间】:2015-09-21 16:20:19 【问题描述】:

给定一个 POD 结构(在 C++03 中)或标准布局类型(在 C++11 中),所有成员都有基本对齐要求,是否保证每个成员都按照它的对齐要求?

换句话说,对于标准布局类型 Sm0 ... mn 中的所有成员 m_k

  struct S 
    T0 m0;
    T1 m1;
    ...
    TN mn;
  ;

以下表达式的计算结果是否保证为true

  (offsetof(S,m_k) % alignof(decltype(S::m_k))) == 0

请给出 C++03 和 C++11 的答案并引用标准的相关部分。来自 C 标准的支持证据也会有所帮助。


我对 C++03 标准 (ISO/IEC 14882:2003(E)) 的解读是,除了第一个成员外,它对 POD 结构中成员的对齐保持沉默。相关段落是:

在规范的语言中,对象是一个“存储区域”:

1.8 C++ 对象模型 [intro.object]

1.8/1 C++ 程序中的构造创建、销毁、引用、访问和操作对象。对象是一个存储区域。 ...

对象根据对齐要求进行分配:

3.9 类型 [basic.types]

3.9/5 对象类型有对齐要求(3.9.1、3.9.2)。一个完整对象类型的对齐是一个实现定义的整数值,表示字节数; 对象分配在满足其对象类型对齐要求的地址。

基本类型有对齐要求:

3.9.1 基本类型 [basic.fundamental]

3.9.1/3 对于每一种有符号整数类型,都存在对应的(但不同的)无符号整数类型:“unsigned char”、“unsigned short int”、“unsigned int”和“unsigned long int”, " 每个都占用相同的存储量和对应的有符号整数类型具有相同的对齐要求(3.9);...

填充可能由于“实现对齐要求”而发生:

9.2 类成员 [class.mem]

9.2/12 在没有中间访问说明符的情况下声明的(非联合)类的非静态数据成员被分配,以便后面的成员在类对象中具有更高的地址。非静态的分配顺序 由访问说明符分隔的数据成员未指定 (11.1)。 实施对齐要求可能会导致两个相邻的成员不能紧挨着分配;管理虚拟功能 (10.3) 和虚拟基类 (10.1) 的空间要求也是如此。

9.2/12 中的“已分配”一词是否与 3.9/5 中的“已分配”具有相同的含义?规范中“已分配”的大多数用法是指动态存储分配,而不是结构内部布局。在 9.2/12 中使用 may 似乎暗示 3.9/5 和 3.9.1/3 的对齐要求可能对结构成员没有严格要求。

POD-struct 的第一个成员将根据结构的对齐要求进行对齐:

9.2/17 指向 POD 结构对象的指针,使用 reinterpret_cast 适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 [注意:因此,在 POD 结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。 ]

[在上述所有引号中都添加了重点。]

【问题讨论】:

对齐要求中的“对象”一词的意思是“对象”,而不仅仅是“最复合的对象”。 (即复合类型的定义,其中它说“包含各种类型的一系列对象的类”。) @rici:谢谢。我从规范中添加了对象的定义:“存储区域”。 罗斯:正确。一个存储区域。不是一个独立的存储区域。复合对象中的对象仍然是对象,因此仍受对齐要求的约束。 9.2/17 中的条件(“可能”)只是承认对齐要求可能需要或可能不需要填充;不允许避免对齐要求。 【参考方案1】:

POD 结构的每个元素本身就是一个对象,对象只能根据这些对象的对齐要求进行分配。但是,对齐要求可能会发生变化,因为某些东西是另一个对象的子对象 ([basic.align]/1, 2:

1 对象类型具有对齐要求(3.9.1、3.9.2),这对可以分配该类型对象的地址施加了限制。 alignment 是实现定义的整数值,表示可以分配给定对象的连续地址之间的字节数。对象类型对该类型的每个对象都有对齐要求;可以使用对齐说明符(7.6.2)请求更严格的对齐。

2 基本对齐由小于或等于所有上下文中实现支持的最大对齐的对齐表示,等于alignof(std::max_align_t) (18.2)。 当一个类型用作完整对象的类型和用作子对象的类型时,类型所需的对齐方式可能不同。 [例子:

struct B  long double d; ;
struct D : virtual B  char c; 

D 是一个完整对象的类型时,它将有一个B 类型的子对象,因此它必须为long double 适当地对齐。如果D 显示为另一个对象的子对象,该对象也将B 作为虚拟基类,则 B 子对象可能是不同子对象的一部分,从而降低了D 子对象的对齐要求。—结束示例] alignof 运算符的结果反映了中的类型 完整对象案例。

[强调添加]

虽然示例通过继承引用子对象,但规范性措辞只是泛指子对象,所以我相信同样的规则适用,所以一方面你可以假设每个子对象都对齐,以便可以访问它。另一方面,不,您不能一定假设这将与alignof 给您的对齐方式相同。

[参考来自 N4296,但我相信这同样适用于所有最新版本。当然,C++98/03 根本没有alignof,但我相信同样的基本原则也适用——成员将对齐以便可以使用,但对齐要求不一定相同就像它们被用作独立对象一样。]

【讨论】:

@JerryCoffin 我的问题是关于 POD 结构和标准布局类型的。因此,尽管很有趣,但我不确定虚拟基类示例如何应用。您认为有什么可以加强我在 POD/SLT 案例中所做的假设吗? @rici 不,它不必是至少。子对象对齐要求的要点是它们可能更弱。 @Columbo:子对象对齐不能小于基本类型的基本对齐。但我同意我的断言需要限定。 @rici 如果“基本类型的基本对齐”是指类型的完整对象对齐要求,那么不,子对象要求可能会更弱(因为标准从未另有说明 - 它们是独立的要求)。也就是说,任何调用的对齐要求是否是基本的并不重要。 @RossBenica 那有什么不同呢?如果通常的对齐方式是 4,为什么我不能在结构中每 1 对齐一个 int?【参考方案2】:

每种类型都有两个不同的对齐要求。一个对应完整对象,另一个对应子对象。 [basic.align]/2:

一种类型在使用时可能需要的对齐方式不同 作为一个完整对象的类型,当它被用作一个对象的类型时 子对象。 [示例

struct B  long double d; ;
struct D : virtual B  char c; ;

D是一个完整对象的类型时,它会有一个子对象 键入B,因此它必须与long double 正确对齐。如果 D 显示为另一个对象的子对象,该对象也具有 B 作为 虚拟基类,B 子对象可能是不同的 子对象,降低了D 子对象的对齐要求。 —结束示例 ] alignof 运算符的结果反映了完整对象情况下类型的对齐要求

我想不出任何关于成员子对象的具体例子——而且肯定没有! -,但上述段落承认成员子对象的对齐方式比alignof 弱的可能性会产生,这会导致您的条件失败。

【讨论】:

以上是关于POD 结构或标准布局类型的成员是不是保证根据其对齐要求对齐?的主要内容,如果未能解决你的问题,请参考以下文章

具有原始类型的单个数组成员的标准布局结构的保证内存布局

std::is_pod

[.NET] 结构体布局详解与结构体内存对齐具体方式

[.NET] 结构体布局详解与结构体内存对齐具体方式

POD 类型的填充字节是不是被复制?

memcpy 一个非 POD 对象