所谓知己知彼,百战不殆。只有深入了解了c++对象的内存布局,我们才能更熟练运用c++这门语言。
运行环境vs2013
一单继承和多继承的结合
class C
{
public:
C() :c(1){}
private:
int c;
};
class B1 :public C
{
public:
B1() :C(), b1(2){}
private:
int b1;
};
class B2 :public C
{
public:
B2() :C(), b2(3){}
private:
int b2;
};
class A :public B1, public B2
{
public:
A() :B1(), B2(), a(4){}
private:
int a;
};
int main()
{
A f1;
cout<<sizeof(f1);
return 0;
}
因为不是virtual继承,继承关系如下:
我们转到内存去看一下对象空间是怎么分布的:
0x00CAF800 01 00 00 00 ....//B1中的c的成员数据
0x00CAF804 02 00 00 00 ....//B1特有的的成员数据
0x00CAF808 01 00 00 00 ....//B2的c的成员数据
0x00CAF80C 03 00 00 00 ....//B2特有的成员数据
0x00CAF810 04 00 00 00 ....//A特有的成员数据
0x00CAF814 cc cc cc cc ????
所以最后的数据分布是这样的:
所以根据对象模型,大家应该知道输出的值是多少了吧。
二.菱形继承
class C
{
public:
C() :c(1){}
private:
int c;
};
class B1 :virtual public C
{
public:
B1() :C(), b1(2){}
private:
int b1;
};
class B2 :virtual public C
{
public:
B2() :C(), b2(3){}
private:
int b2;
};
class A :public B1, public B2
{
public:
A() :B1(), B2(), a(4){}
private:
int a;
};
int main()
{
A aa;
B1 bb1;
B2 bb2;
C cc;
cout << sizeof(aa);//24
cout << sizeof(bb1);//12
cout << sizeof(bb2);//12
cout << sizeof(cc);//4
return 0;
}
正如其名字,继承关系如下:
首先我们还是转到内存去看看对象空间如何分布的
取类A的对象的地址,得到:
0x0114FC84 70 dd 83 00 //????
0x0114FC88 02 00 00 00 //B1中特有的数据
0x0114FC8C ac db 83 00 //????
0x0114FC90 03 00 00 00 //B2中特有的数据
0x0114FC94 04 00 00 00 //A中特有的数据
0x0114FC98 01 00 00 00 //C中特有的数据
仅仅根据这一段内存地址,显然,我们不知道0x0114FC84,0x0114FC8C中的类似地址的数据有何作用。既然如此我们就到该地址上一探究竟:
0x0083DD70 00 00 00 00 ....
0x0083DD74 14 00 00 00 ....//20
0x0083DBAC 00 00 00 00 ....
0x0083DBB0 0c 00 00 00 ....//12
联系上面,我们可以得知,改地址指向的内容就是B1和B2类到类A数据的偏移量。
最后我们可以得出数据分布:
三.有虚函数的virtual继承
class B1
{
public:
B1() :b1(1){}
virtual void fun1(){}
virtual void fun2(){}
private:
int b1;
};
class B2
{
public:
B2() :b2(2){}
virtual void fun3(){}
virtual void fun4(){}
private:
int b2;
};
class A :public B1, public B2
{
public:
A() :B1(), B2(), a(3){}
void fun1(){};
void fun2(){};
void fun3(){};
virtual void fun5(){};
private:
int a;
};
int main()
{
A f1;
cout<<sizeof(f1);//20
return 0;
}
关于虚表,可以参见我的上一篇博客。
由代码可知,继承关系如下:
在这里大家不妨猜测一下对象空间内是怎样的
转到内存:
0x00FBFDDC 90 cc 0f 00 ??..//虚表地址
0x00FBFDE0 01 00 00 00 ....//类B1的成员变量b1
0x00FBFDE4 a4 cc 0f 00 ??..//虚表地址
0x00FBFDE8 02 00 00 00 ....//类B2的成员变量b2
0x00FBFDEC 03 00 00 00 ....//类A的成员变量a
那么第一个虚表指针指向的虚表中到底有哪几个函数,第二个虚表中是哪几个函数呢?
下面一起进入虚表看看:
第一个虚表
0x000FCC90 07 13 0f 00 ....//A::fun1()
0x000FCC94 de 13 0f 00 ?...//A::fun2()
0x000FCC98 45 11 0f 00 E...//A::fun5()
0x000FCC9C 00 00 00 00 ....
通过运行这三个函数可知分别为A::fun1(),A::fun2(),A::fun5()
第二个虚表
0x000FCCA4 ff 10 0f 00 ....//A::fun3()
0x000FCCA8 ca 13 0f 00 ?...//B2::fun4()
0x000FCCAC 00 00 00 00 ....
通过运行这三个函数可知分别为A::fun13(),B::fun4()
因此我们可以得出一个结论:在此类继承中,对象空间会维护两张虚表,并且派生类中新增的虚函数会放在第一张虚表中。
最后我们可以得出数据分布:
四.有虚函数的菱形继承
class C
{
public:
virtual void fun(){}
private:
int c;
};
class B1 :virtual public C
{
public:
virtual void fun(){}
virtual void fun1(){}
private:
int b1;
};
class B2 :virtual public C
{
public:
virtual void fun(){}
virtual void fun2(){}
private:
int b2;
};
class A :public B1, public B2
{
public:
void fun(){}
private:
int a;
};
typedef void(*fun)();
void funtest(A&b)
{
int i = 0;
int*p = (int*)*((int*)(&b)+3);
while (*p)
{
fun fn = (fun)*p;
fn();
p++;
}
}
int main()
{
C f1;
B1 f2;
B2 f3;
A f4;
cout << sizeof(f1);//8
cout << sizeof(f2);//20
cout << sizeof(f3);//20
cout << sizeof(f4);//36
return 0;
}
继承关系如下
由于C对象的结构比较复杂,先从B1开始分析
1.B1
首先注意这里并没有构造函数,如果有了构造函数,结构就要变化了。
通过监视窗口给c,b1赋值为1,2
打开内存:
0x005FFCA8 7c cc 1e 01 |?..//????
0x005FFCAC 90 cc 1e 01 ??..//????
0x005FFCB0 02 00 00 00 ....//类B1数据b1
0x005FFCB4 88 cc 1e 01 ??..//????
0x005FFCB8 01 00 00 00 ....//类C数据c
我们不知道内存中的这三个地址代表什么,只能到该地址上去查看
(1)0x005FFCA8 7c cc 1e 01
0x011ECC7C 64 10 1e 01 d...
0x011ECC80 00 00 00 00 ....
显然这是一张虚表,该函数是B1中新增的函数fun1()
(2)0x005FFCAC 90 cc 1e 01
0x011ECC90 fc ff ff ff ?...
0x011ECC94 08 00 00 00 ....
这是一个偏移量,表示到基类数据的偏移
(3)0x005FFCB4 88 cc 1e 01
0x011ECC88 28 10 1e 01 (...
0x011ECC8C 00 00 00 00 ....
是一张虚表,该函数是B1中重写的fun( )
由此可得B1的对象数据分布:
注意:如果B1中没有新增函数,那么B1对象只维护一张虚表;
如果B1中有新增函数,那么B1对象维护两张虚表
2.A
0x002EFDE4 08 dd 2e 01 .?..//类B1虚表指针
0x002EFDE8 28 dd 2e 01 (?..//到基类数据的偏移
0x002EFDEC 02 00 00 00 ....//类B1特有数据
0x002EFDF0 14 dd 2e 01 .?..//类B2虚表指针
0x002EFDF4 34 dd 2e 01 4?..//到基类数据的偏移
0x002EFDF8 03 00 00 00 ....//类B2特有数据
0x002EFDFC 04 00 00 00 ....//类B2特有数据
0x002EFE00 20 dd 2e 01 ?..//类C虚表指针
0x002EFE04 01 00 00 00 ....//类C特有数据
(1)0x002EFDE4 08 dd 2e 01
0x012EDD08 69 10 2e 01 i...
0x012EDD0C 00 00 00 00 ....
通过运行这个虚表上的这个函数可知该函数为B1::fun1()
(2)0x002EFDF0 14 dd 2e 01
0x012EDD14 32 10 2e 01 2...
0x012EDD18 00 00 00 00
通过运行这个虚表上的这个函数可知该函数为B2::fun2()
(3)0x002EFE00 20 dd 2e 01
0x012EDD20 64 10 2e 01 d...
0x012EDD24 00 00 00 00 ....
通过运行这个虚表上的这个函数可知该函数为 A::fun()
最后可得到类A的对象数据分布
此时A的对象维护了三张虚表,但我们并不是很清楚这三张虚表的具体作用,我们可以在类中加入虚函数来测试一下:
类C中加入函数virtual void fun3();
类B1加入函数virtual void fun4();
类B2加入函数virtual void fun5();
类A加入函数virtual void fun6();
此时的继承关系如图
调用各虚表中的函数,得到对象模型为
由此可知:A的对象一共维护三张虚表。
第一张是B1的虚表,包含了类B1中新增的虚函数,以及类A中新增的虚函数。
第二张是B2的虚表,包含了类B2中新增的虚函数。
第三张是C的虚表,包含类C的虚表,如果类C的派生类对虚表上的函数进行重写,该函数地址变为重写后的地址