C++类对象内存布局

本文通过分析内存布局来了解编译器对类的内存布局的安排。分析方法为先贴出代码,在给出相应内存的内容,最后给出分析结果。

以下运行环境为VS2005,不同编译器实现可以不一样(一般是因为C+标准并未要求)。具体可参见《深度探索C++对象模型》。

1.没有虚函数和虚基类的继承

单一继承
代码如下:
class A{
public:
	A(){_a = 0x00;}
	int _a;
};
class B:public A{
public:
	B(){_b = 0x11;}
	int _b;
};
int _tmain()
{	
	A a;
	B b;
}
对应对象a和b的内存区为:
0x0012FF38  cc cc cc cc  ....
0x0012FF3C  00 00 00 00  ....对象b
0x0012FF40  11 00 00 00  ....

0x0012FF44  cc cc cc cc  ....
0x0012FF48  cc cc cc cc  ....
0x0012FF4C  00 00 00 00  ....对象a
0x0012FF50  cc cc cc cc  ....
结论:
1.运行时栈上的自动对象在定义时相邻,但是内存地址不一定相邻。
2.先声明的变量占有高地址,后声明的在低地址处。
3.对于对象b,其继承而来的部分在上,类B独有的部分在下。这样可以和C的结构体兼容,但是C++标准并未强制规定必须如此布局(但一般编译器都是这么做的)。

多继承

代码如下:
class A{
public:
	A(){_a = 0x00;}
	int _a;
};
class B{
public:
	B(){_b = 0x11;}
	int _b;
};
class C:public A, public B{
public:
	C(){_c = 0x22;}
	int _c;
};
int _tmain()
{	
	A a;
	B b;
	C c;
}
对象c的内存区为:
0x0012FF28  cc cc cc cc  ....
0x0012FF2C  00 00 00 00  ....对象c
0x0012FF30  11 00 00 00  ....
0x0012FF34  22 00 00 00  "...

0x0012FF38  cc cc cc cc  ....
结论:
1.对象按声明的继承顺序排列,如下面的对象c布局为:继承自类A的成员-》继承自类B的成员-》类C自己定义的成员。若改为下面的声明:
class C:public B,public A{...
则c布局变为:继承自类B的成员-》继承自类A的成员-》类C自己定义的成员。

2.含有虚函数的继承

代码如下:
class A{
public:
	A(){_a = 0x00;}
	int _a;
	virtual void print(){}
};
class C:public A{
public:
	C(){_c = 0x22;}
	int _c;
};
int _tmain()
{	
	A a;
	C c;
}
对象c的内存区为:
0x0012FF30  cc cc cc cc  ....
0x0012FF34  78 3d 48 00  x=H.对象c
0x0012FF38  00 00 00 00  ....
0x0012FF3C  22 00 00 00  "...

0x0012FF40  cc cc cc cc  ....
0x0012FF44  cc cc cc cc  ....
0x0012FF48  70 3d 48 00  p=H.对象a
0x0012FF4C  00 00 00 00  ....

0x0012FF50  cc cc cc cc  ....

结论:
1.调试窗口可以看到对象a有两个成员:_c和__vfptr。很明显后者为编译器自动增加用来支持虚函数的(后面用__vfptr表示该4字节)。你可以试试自己添加个名称为__vfptr的变量,看看有啥效果。(内存有它的位置,但你却没法引用它,因为编译器将该符号绑定到特定的位置了(该处是对象第一个字节)。
2.类中仅有一个__vfptr。所以每个类仅有一个虚函数表,表可以理解成一个void*型的数组,其中每一项为一个虚函数地址。这个数组大小是固定的,一般取决于虚函数的个数,位置则按声明时的位置来定,子类的虚函数表中,基类所定义的虚函数在该表的上面(索引较小处)。一次排列。
3.继承之后,__vfptr也只有一个,由此我们想到,当用子类初始化父类时,__vfptr需要编译器添加指令来维护其值。例如:
a=c;//此时,a获得c继承值a的部分,但不包括__vfptr的值。有兴趣的可以看看该语句的汇编代码。

下面分析下这种情况下得虚函数表,代码如下:
class A{
public:
	A(){_a = 0x00;}
	int _a;
	virtual void printA(){}
};
class C:public A{
public:
	C(){_c = 0x22;}
	int _c;
	virtual void printC(){}
};
int _tmain()
{	
	A a;
	C c;
	c._a = 0xffff;
	a = c;
}
虚函数表为(地址从相应对象的第一个字获得):
A类:
0x00483D70  13 b1 42 00  ..B.下划线数所表示的地址处是一条跳转指令,跳到printA。
0x00483D74  00 00 00 00  ....
C类:
0x00483D7C  13 b1 42 00  ..B.明显,地址一样,且在虚函数表中索引一样,如果重写了printA,这里的地址就会变。
0x00483D80  af b5 42 00  ..B.这个项表示C中添加的虚函数printC,同样是跳转指令
0x00483D84  00 00 00 00  ....


3.虚基类继承

class A{
public:
	A(){_a = 0x00;}
	int _a;
	//virtual void printA(){}
};

class C:virtual public A{
public:
	C(){_c = 0x22;}
	int _c;
	//virtual void printB(){}
};
int _tmain()
{	
	A a;
	C c;
}

0x0012FF34  cc cc cc cc  ....
0x0012FF38  6c 3d 48 00  l=H.用于支持虚基类的成员
0x0012FF3C  22 00 00 00  "...
0x0012FF40  00 00 00 00  ....

0x0012FF44  cc cc cc cc  ....
0x0012FF48  cc cc cc cc  ....
0x0012FF4C  00 00 00 00  ....
0x0012FF50  cc cc cc cc  ....
那上面的4字节是如何支持虚基类的了,下面看一个赋值操作:
	A* pa= NULL;
	pa = &c;//这一条
上面的语句翻译成汇编结果为:
pa = &c;
0042D705  lea         eax,[c] c的地址给eax
0042D708  test        eax,eax 这个其实是用来测试c的地址是不是为0,这里肯定不会为0,但是如果用C的指针来赋给pa就不一定了
0042D70A  jne         wmain+78h (42D718h) 
0042D70C  mov         dword ptr [ebp-0F0h],0 c的地址为0
0042D716  jmp         wmain+88h (42D728h) 

0042D718  mov         ecx,dword ptr [c] 这里就是上面红色地方的内容了,完成后ecx=0x00483d6c
0042D71B  mov         edx,dword ptr [ecx+4] 内存中的内容如下,完成后edx=8,可想而知这8应该是C类独有部分的大小(4字节)和支撑部分的大小(4字节,红色区域)
0042D71E  lea         eax,c[edx] 计算c中a子对象的偏移
0042D722  mov         dword ptr [ebp-0F0h],eax 
0042D728  mov         ecx,dword ptr [ebp-0F0h] 
0042D72E  mov         dword ptr [pa],ecx 

0x00483D68  d8 71 06 00  .q..
0x00483D6C  00 00 00 00  ....
0x00483D70  08 00 00 00  ....
0x00483D74  00 00 00 00  ....
因此,支撑部分存放的有该类虚基类部分在类中的偏移。我猜有两个虚基类时,这里会有2项。我加了个虚基类后支撑部分变成下面部分:
0x00483D68  d8 71 06 00  .q..
0x00483D6C  00 00 00 00  ....
0x00483D70  08 00 00 00  ....
0x00483D74   0c 00 00 00  ....
0x00483D78  00 00 00 00  ....
0x00483D7C  55 6e 6b 6e  Unkn

如果同时还有虚函数了。。






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值