C++中的类布局:为什么成员有时会有顺序?

9

C++标准规定,单个访问部分中的成员变量必须按照它们声明的顺序在内存中排列。同时,编译器可以自由选择访问部分之间的相互顺序。这种自由性理论上使得无法链接由不同编译器创建的二进制文件。那么,严格的部分内排序仍有哪些原因呢?而且,即将到来的C++09 新的C++11标准是否提供了一种“手动”完全确定对象布局的方式?


C++11 对此进行了修订,以澄清中间的 重复 访问说明符(无论出于何种原因)不会妨碍布局保证;只有当访问级别通过 不同的 说明符而非先前的重复发生改变时才会受到影响。 - underscore_d
4个回答

8
这种自由使得理论上不可能链接由不同编译器创建的二进制文件。这是不可能的原因有很多,结构布局只是其中最小的问题。虚函数表、operator newdelete的实现、数据类型大小等等。
那么,为什么要严格按照部分顺序排列呢?
我认为是为了C兼容性,以便在给定编译器集的情况下,在C++中定义的结构体与在C中打包的方式相同。
新的C++11标准是否提供了一种通过手动完全确定对象布局的方法?
没有,就像当前标准一样。
对于一个没有虚函数表并且完全私有(或公共)字段的classstruct,如果使用[u]int[8|16|32|64]_t类型,则已经可以实现。你需要比这更多的用例吗?

你需要更多的用例是什么?优化。通过重新排列对象的数据成员,可以使它们变得更小。代码中的顺序通常反映了成员的逻辑分组,因此您不希望改变它。 - Koen Van Damme

2

[编辑] 今天我学到了新东西!找到了以下标准引用:

未经访问说明符声明的(非联合)类的非静态数据成员分配在类对象内,以便后续成员在类对象内具有更高的地址。由访问说明符分隔的非静态数据成员的分配顺序是未指定的(11.1)。实现对齐需求可能导致两个相邻的成员不会立即分配在一起;管理虚拟函数(10.3)和虚拟基类(10.1)的空间也可能导致这种情况。

有趣 - 我不知道为什么要给予这种程度的自由。继续我的先前的回复...


如上所述,保留排序的原因是为兼容C,并且当时我认为没有人想到重新排列成员的好处,而且内存布局通常是手工完成的。此外,现在被认为是“丑陋技巧”的东西(例如使用memset零化选定的成员或具有相同布局的两个结构体)曾经很常见。

标准不提供强制执行给定布局的方法,但大多数编译器提供控制填充的措施,例如在MSVC编译器上使用#pragma pack。

自动填充的原因是平台可移植性: 不同的架构具有不同的对齐要求,例如一些架构在错位的整数上抛出异常(那时这些情况是简单的)。


不,我很确定他是对的。我没有访问这里的标准来查找它,但我之前也注意到了这个问题。它说在不同的访问部分之间排序是未指定的,但在一个部分内,成员按声明顺序排列。我也曾想过这样做的意义所在。 - jalf
你说得对,我已经更新了回复。现在我也很好奇。 - peterchen
1
C++11 对此进行了修订,以澄清中间的 重复 访问说明符(无论出于何种原因)不会妨碍布局保证;只有当访问级别通过 不同的 说明符而非先前的重复发生改变时才会受到影响。 - underscore_d

0

你永远不应该链接由不同编译器创建的对象。即使你所说的内容发生了改变,你仍然会遇到更多的问题,阻止你与另一个编译器生成的文件进行链接(例如对齐、名称混淆、调用约定等等)。

编译器自由地对访问部分进行排序的一个原因可能是为了建立访问部分的顺序:具有较低地址的成员比具有较高地址的成员更受保护,例如。

如果不允许重新排序,你将不会获得任何好处:只有POD提供C兼容性并提供一种方法来给出类/结构体中成员的字节偏移量(使用宏offsetof)或允许你对它们进行memcpy。如果定义了自定义构造函数、复制构造函数、私有成员或其他一些东西,类型将变为非POD。特别是,从类派生目前会破坏PODness。

C++1x降低了POD的要求。例如,在C++1x中,std::pair<T, U>实际上是一个POD,即使它提供了自己的构造函数(虽然必须符合某些规则)。


0

编辑:恐怕我误解了你的问题

我认为这是一种内存访问优化。 例如,如果我们有这个结构:

struct example
{
   int16 intData;
   byte  byteData;
   int32 intData;
   byte intData;
   int32 intData;
}

假设此平台上的字长为32位。然后,你需要4个完整的字来传递结构中的所有数据:

int16 + byte = 24位(下一个字段不适合在此处)

int32 = 32位(下一个字段不适合在此处)

byte = 8位(下一个字段不适合在此处)

int32 = 32位

但如果你重新排列字段:

struct example
{
   int16 intData;
   byte  byteData;
   byte intData;
   int32 intData;
   int32 intData;
}

那么你就可以节省一次内存访问。


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