现在的位置: 首页 > 综合 > 正文

c++的类的内存布局

2013年04月27日 ⁄ 综合 ⁄ 共 2683字 ⁄ 字号 评论关闭

转自:http://blog.sina.com.cn/s/blog_b32586610101btqv.html

c++内存布局规则如下:


1. Nonstatic data member 存放在Class Object;
2. Static data member, static/nonstatic member function存放在class object之外.

//////////////////////////////////////////////////////////

所有实例运行于32位机器上测试

/////////////////////////////////////////////////////////

· 规则12:            
class A
{
public:
 int a;
 static char b;
 void foo();
 static void bar();
};
ASSERT(sizeof(A) == 4);
那么在每一个A对象内,只含有一个a,也就是,sizeof(A) == .
b,foo(),bar(),都不在class object之内,他们在内存中有唯一实体.

· 
3. 若类有virtural function, 则在class object 中增加virtual pointer(vptr)指向virtural function tabel(vtbl). vptr在类中的位置有两种情况:1是在所有成员变量之后,这么做的好处是这个类能和c语言兼容.2是在最前面,这样做的话,虚拟继承等实现会方便一点,但是不能和c兼容.在前或后依赖编译器实现.

· 规则3
class B
{
public:
 int a;
 virtual void foo();
};
ASSERT(sizeof(B) == 8);
一个int和一个vptr,8
4. nonstatic data member 若在同一个access section{存取区段,publicprivateprotected三种段落}则变量在内存中的顺序保证和声明中的顺序一致(较晚出现的变量有较高的地址).而不同access section中的数据顺序则没有保证,依赖编译器实现.

· 规则4,没啥好说的,一般就算在不同的access section,都会按照一致的顺序来声明.但是要注意顺序一致不代表连续.因为变量间可能会有一些bytes用于内存对齐.
5. 内存对齐类的各个成员,第一个成员位于offset{偏移位置}0的位置,以后每个数据成员的偏移量必须是min(#pragma pack(), 这个数据成员自身的长度)的倍数数据成员自身对齐后类本身也要进行对齐对齐将按照min(#pragma pack(), 类中长度最大的成员)的倍数进行.

· 规则5
class C
{
 int a;
 char b;
};
ASSERT(sizeof(C) == 8);
规则5有两条子规则,第一条对这个例子没用,经过第一条后C的大小还是5,可是第二条要求整个类要对齐,那么必须在char b后增加3bytes.
如果intchar的声明顺序反一下,那么为满足第一条规则,类已经需要对齐成8,已经是8那么第二条也满足了.

· 
6. 如果类为空,编译器会安插1byte的数据到类中,以确保类的每个实例都会有唯一的内存地址

· 规则6
class D
{};
ASSERT(sizeof(D) == 1);
1byte是编译器插进去的,如果不插的话,连续声明a,b;再取他们的地址,就会变成一样的了.就无法分辨哪个变量是哪个了.
不过要注意的是,任何类继承了D,只要里面有vptr或者任何一个变量,那么编译器就不会在子类中加入这byte.(这个是依赖于编译器的,而不是标准规定.如果编译器没有去掉这1byte的话,那么就要内存对齐了.)

· 
7. 继承后,子类的数据成员不会占用父类内存对齐用的空间C++语言保证:"出现在derivd class{衍生类} 中的base class subobject{基类子对象}有其完整的原样性"

· 规则7

· class C
{
 int a;
 char b;
};

class E:public C
{
 int c;
 char d[2];
};
ASSERT(sizeof(E) == 16);
编译器不会为了节省空间把E的成员插入到C为了内存对齐的而补出的空间中的.这道题我面试的时候被问过,我答16的时候面试官还认为错了,太浪费空间了.但是这的确是唯一的正确解.

· 
8. 如果类的继承体系不是单一,而是多重继承,但是不含虚拟继承,那么有多少条继承链,内存布局中就有多少个vptr.多个继承链的位置,和继承时的声明顺序一致.

· 规则8

· class B
{
public:
 int a;
 virtual void foo();
};

class F
{
 int b;
 virtual void bar();
};
class G:public B, public F
{
 int c;
};
ASSERT(sizeof(G) == 20);
3int,2vptr,一共20

· 
9. 如果使用了虚拟继承,则先将derived class的不变部分布局,然后再布局虚拟继承的base class,而具体布局则有以下情况:
  1. 使用Pointer Strategy, 每一个虚拟继承的类,都有一个额外的指针指向base class
  2. 使用Virtual table offset strategy, 不加入额外的指针指向base class,而是在vtbl-1offset内放置该类与虚拟继承的基类之间的offset. 这样的话运行时则可以通过derivedPointer vptr[-1]得到.
  3. 使用Virtual base class table. 这个是微软的做法,不过书中并没有具体描述怎么做,根据我的理解好像是在vtbl中加入一个指针指向base class.
 如果采用23, 那么内存布局和8并不会有太大区别,就是virtual base class跑到最后面去了.
 9种情况异常复杂,建议看原书外加自己在多个编译器上实践实践.

规则9
class H:virtual public B
{
int a;
};
class I:virtual public B
{
int b;
};
class J:public H, public I
{

抱歉!评论已关闭.