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

时间:2008-11-14 09:06:14

标签: c++

C ++标准规定单个访问部分中的成员变量必须按照它们声明的顺序在内存中进行布局。同时,编译器可以自由选择访问部分本身的相互排序。这种自由使理论上不可能链接由不同编译器创建的二进制文件。那么严格的剖面订购的剩余原因是什么? 即将推出的C ++ 09 新C ++ 11标准是否提供了一种“手动”完全确定对象布局的方法?

4 个答案:

答案 0 :(得分:8)

  

这种自由使理论上无法链接不同编译器创建的二进制文件。

由于多种原因,这是不可能的,结构布局是最小的。 vtable,operator newdelete的实现,数据类型大小......

  

那么严格的剖面排序的剩余原因是什么?

C兼容性,我想,因此C包中定义的结构与在给定编译器集的C ++ 中的结构相同。

  

新的C ++ 09 11标准是否提供了一种“手动”完全确定对象布局的方法?

不,不超过现行标准。

对于没有vtable和完全私有(或公共)字段的classstruct,如果您使用[u]int[8|16|32|64]_t类型,则已经可以。你有什么用例呢?

答案 1 :(得分:2)

[编辑] 我今天学到了新东西!发现以下标准报价:

  

a的非静态数据成员   (非联合)类声明没有   干预访问说明符   分配,以便以后的成员   一个班级中较高的地址   宾语。分配顺序   由...分隔的非静态数据成员   access-specifier未指定   (11.1)。实施一致   要求可能导致两个相邻   成员不得分配   紧接着彼此;可能   管理空间要求   虚函数(10.3)和虚函数   基类(10.1)。

有趣的是 - 我不知道为什么会给出这种程度的自由。继续我以前的回复......


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

该标准没有为您提供强制执行给定布局的方法,但大多数编译器都提供了控制填充的措施,例如: MSVC编译器上的#pragma pack。

自动填充的原因是平台可移植性:不同的体系结构具有不同的对齐要求,例如:一些架构会抛出未对齐的整数(这些都是当时的简单案例)。

答案 2 :(得分:0)

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

我认为这是内存访问的优化。 例如,如果我们得到这个结构:

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

假设这个平台中的单词是32位。然后,您将需要4个完整的单词来传递结构中的所有数据:

  

int16 + byte = 24位(netx字段不适合此处)

     

int32 = 32位(netx字段不适合此处)

     

byte = 8位(netx字段不适合此处)

     

int32 = 32位

但是如果你将字段重新排列为:

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

然后你可以保存一个内存访问。

答案 3 :(得分:0)

您永远不应该链接由不同编译器创建的对象。即使您所谈论的内容发生了变化,您仍然会遇到更多阻碍您与其他编译器生成的文件链接的问题。 (对齐,命名修改,调用约定只列出其中的一些)。

编译器可以自由地订购访问部分的一个原因可能是编译器可以为访问部分建立订单:例如,具有较低地址的成员比具有较高地址的成员更受保护。

如果不允许重新排序,你将无法获得任何东西:只有POD提供C兼容性,并且可以为类/结构内的成员提供字节偏移(使用宏offsetof)或允许您记住他们。如果您定义自定义构造函数,复制构造函数,私有成员或其他一些东西,则类型将变为非POD。特别是,从类中派生出来会打破PODness。

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