深度探索c++对象模型

所谓知己知彼,百战不殆。只有深入了解了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的派生类对虚表上的函数进行重写,该函数地址变为重写后的地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值