C++ 虚拟继承对象布局

自己做实验的时候发现和一些文章上面的有点不一样。

#include <iostream>
using namespace std;


class B
{
public:
    long ib;
    long cb;//全都定义成 long 这个方便看内存,不然不是对齐的
public:
    B() :ib(0x1010), cb(0x1010) {}
    virtual void f() { cout << "B::f()" << endl; }
    virtual void Bf() { cout << "B::Bf()" << endl; }
};

class B1 : virtual public B
{
public:
    long ib1;
    long cb1;
public:
    B1() :ib1(0x1111111), cb1(0x1111) {}
    //virtual void f() { cout << "B1::f()" << endl; } //为什么要注释掉呢,后面说,不注释掉是失败的 是运行不了的
    virtual void f1() { cout << "B1::f1()" << endl; }
    virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};


int main()
{
    typedef void(*Fun)(void);
    long long** pVtab = NULL;
    Fun pFun = NULL;
    B1 bb1;
    cout<<hex;
    pVtab = (long long**)&bb1;
    cout<<"offset:"<<(long)pVtab-(long)(B*)&bb1<<endl;//因为存在偏移 (B*)&bb1 是不等于 &bb1 这个地址的。
    cout << "[0] B1::_vptr->" << endl;
    for(int i=0;(Fun) pVtab[0][i]!=NULL;i++){
        cout<<"\t["<<i<<"]\t";
        pFun=(Fun)pVtab[0][i];
        pFun();
    }
    cout << "[2] B1::ib1 = ";
    cout << (long)pVtab[1]<< endl; //B1::ib1
    cout << "[3] B1::cb1 = ";
    cout << (long)pVtab[2] << endl; //B1::cb1

    cout << "[3] B::_vptr->" << endl;
    for(int i=0;i<2;i++){ //就是这个地方,如果前面不注释掉就会报错
    //还有你没有没有注意到细节 ,我这个地方写的是 <2 并不是 函数为 空?
        cout<<"\t["<<i<<"]\t";
        pFun=(Fun)(pVtab[3][i]);
        pFun();
    }
    cout << "[4] B::ib1 = ";
    cout << (long)pVtab[4]<< endl; //B1::ib1
    cout << "[5] B::cb1 = ";
    cout << (long)*(pVtab+5) << endl; //B1::cb1
    return 0;
}

输出结果

offset:ffffffffffffffe8
[0] B1::_vptr->
	[0]	B1::f1()
	[1]	B1::Bf1()
[2] B1::ib1 = 1111111
[3] B1::cb1 = 1111
[3] B::_vptr->
	[0]	B::f()
	[1]	B::Bf()
[4] B::ib1 = 1010
[5] B::cb1 = 1010

GDB 调试 查看内存,有意思的来了。

(gdb) p pVtab
$1 = (long long **) 0x7ffdf9b52fc0 //一个B1对象的具体内存
(gdb) x /10xg 0x7ffdf9b52fc0
0x7ffdf9b52fc0:	0x0000562080dd6d08(这个是第一个虚表指针)	0x0000000001111111
0x7ffdf9b52fd0:	0x0000000000001111	0x0000562080dd6d38(第二个虚表指针)
0x7ffdf9b52fe0:	0x0000000000001010	0x0000000000001010
0x7ffdf9b52ff0:	0x0000562080bd61c0	0x0000562080bd617e
0x7ffdf9b53000:	0x00007ffdf9b52fc0	0x0000000200000000

有的文章里面说,虚拟继承每个基类会创建一个虚表,实际上并没有,只是多了一个虚表指针并没有那么多神奇的玩意。
你可以发现两个地址挨着非常近。我们查看 0x0000562080dd6d08这个的内存。

gdb) x /10xg 0x0000562080dd6d08
0x562080dd6d08 <vtable for B1+24>:	0x0000562080bd6146	0x0000562080bd617e
0x562080dd6d18 <vtable for B1+40>:	0x0000000000000000	0x0000000000000000
0x562080dd6d28 <vtable for B1+56>:	0xffffffffffffffe8	0x0000562080dd6d78
0x562080dd6d38 <vtable for B1+72>:	0x0000562080bd607e(第二个虚表指针在这???0x0000562080bd60b6
0x562080dd6d48 <VTT for B1>:	0x0000562080dd6d08(有没有发现这个地址就是第一个虚表指针,并没有NULL,所以我那个地方写的是i<2)	0x0000562080dd6d38

看到了吗,实际上全都在B1的虚表里面,只是指针指向的位置不一样。还有个更有意思的。
有没有发现有两个0x0000000000000000目前不知道是啥。0xffffffffffffffe8这个刚好就是偏移量,并没有什么指针直接指向他。所以我不知道虚基表指针是啥,也没看到一个指针算进对象的空间。

然后再继续看这两个虚表指针的内容。

(gdb) x /10xg 0x0000562080bd6146 //不出意外是个函数
0x562080bd6146 <B1::f1()>:	0x10ec8348e5894855	0xfb358d48f87d8948
0x562080bd6156 <B1::f1()+16>:	0x0f003d8d48000000	0x48fffff97be80020
0x562080bd6166 <B1::f1()+32>:	0x200e89058b48c289	0xe8d78948c6894800
0x562080bd6176 <B1::f1()+48>:	0x90c3c990fffff976	0x10ec8348e5894855
0x562080bd6186 <B1::Bf1()+8>:	0xcc358d48f87d8948	0x0ec83d8d48000000

(gdb) x /10xg 0x0000562080bd607e //第二个指针也是
0x562080bd607e <B::f()>:	0x10ec8348e5894855	0xb4358d48f87d8948
0x562080bd608e <B::f()+16>:	0x0fc83d8d48000001	0x48fffffa43e80020
0x562080bd609e <B::f()+32>:	0x200f51058b48c289	0xe8d78948c6894800
0x562080bd60ae <B::f()+48>:	0x90c3c990fffffa3e	0x10ec8348e5894855
0x562080bd60be <B::Bf()+8>:	0x83358d48f87d8948	0x0f903d8d48000001

然后就是更有意思的。记得我们原本注释的吗,我们把他取消注释再来GDB调试。

(gdb) x /10xg 0x000055aec160519d //因为重新运行了地址不一样,我们像那样看第二个虚指针第一项的值
0x55aec160519d <virtual thunk to B1::f()>:	0xebe87a0349178b4c	0xec8348e5894855c0
//看到了  virtual thunk  ,这个告诉我们这个已经被重写了,然后又会调用B1::f()

是不是发现这个布局很像这种写法。

class B{
	A a;
}

当然只是有点像,到了菱形继承就差距很大了。我们继续探索。

#include <iostream>
using namespace std;


#include <iostream>
using namespace std;
class B
{
public:
    long ib;

public:
    B() : ib(0xbbbbb)
    {}

    virtual void
    f()
    {
        cout << "B::f()" << endl;
    }
    virtual void Bf() { cout << "B::Bf()" << endl; }
};

class B1 : virtual public B
{
public:
    long ib1;

public:
    B1() : ib1(0x1111)
    {
    }
    virtual void f(){cout << "B1::f()" << endl;}
    virtual void f1() { cout << "B1::f1()" << endl; }
    virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};

class B2 : virtual public B
{
public:
    long ib2;

public:
    B2() : ib2(0x2222)
    {
    }
//    virtual void f(){cout << "B2::f()" << endl;}
    virtual void f2() { cout << "B2::f2()" << endl; }
    virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};

class D : public B1,
          public B2
{
public:
    long id;
public:
    D() : id(0xdddd)
    {
    }
    virtual void
    f()
    {
        cout << "D::f()" << endl;
    }
    virtual void f1() { cout << "D::f1()" << endl; }
    virtual void f2() { cout << "D::f2()" << endl; }
    virtual void Df() { cout << "D::Df()" << endl; }
};


int main()
{
    typedef void(*Fun)(void);
    long long** pVtab = NULL;
    Fun pFun = NULL;
    D d;

    cout<<"D size:"<<sizeof(d)<<endl;
    cout<<"B1 size:"<<sizeof(B1)<<endl;
    cout<<"D address:"<<&d<<endl;
    cout<<"B1 address:"<<(B1*)&d<<endl;
    cout<<"B1-B2 offset:"<<(long)(B1*)&d-(long)(B2*)&d<<endl;
    cout<<"B2-B offset:"<<(long)(B2*)&d-(long)(B*)&d<<endl;
    cout<<"B address:"<<(B*)&d<<endl;
    pVtab = (long long**)&d;
    cout<<hex;
    cout << "[0] B1::_vptr->" << endl;
    for(int i=0;i<5;i++){
        cout<<"\t["<<i<<"]\t";
        pFun=(Fun)pVtab[0][i];
        pFun();
    }
    cout << "[1] B1::ib1 = ";
    cout << (long)pVtab[1]<< endl;
    cout << "[2] B2::_vptr->" << endl;
    for(int i=0;i<2;i++){
        cout<<"\t["<<i<<"]\t";
        pFun=(Fun)pVtab[2][i];
        pFun();
    }
    cout << "[3] B2::ib2 = ";
    cout << (long)pVtab[3]<< endl;

    cout << "[4] B::ib = ";
    cout << (long)pVtab[4]<< endl;
    cout << "[5] B::_vptr->" << endl;
    for(int i=1;i<2;i++){
        cout<<"\t["<<i<<"]\t";
        pFun=(Fun)pVtab[5][i];
        pFun();
    }
    cout << "[6] B::ib = ";
    cout << (long)pVtab[6]<< endl;
    return 0;
}

运行结果

D size:56
B1 size:32
D address:0x7fff13a26610
B1 address:0x7fff13a26610
B1-B2 offset:-16
B2-B offset:-24
B address:0x7fff13a26638
[0] B1::_vptr->
	[0]	D::f()
	[1]	D::f1()
	[2]	B1::Bf1()
	[3]	D::f2()
	[4]	D::Df()
[1] B1::ib1 = 1111
[2] B2::_vptr->
	[0]	D::f2()
	[1]	B2::Bf2()
[3] B2::ib2 = 2222
[4] B::ib = dddd
[5] B::_vptr->
	[1]	B::Bf()
[6] B::ib = bbbbb

GDB查看内存。

(gdb) x /10xg pVtab
0x7ffc64cadc10:	0x000055c32e665b88(B1的虚指针)	0x0000000000001111
0x7ffc64cadc20:	0x000055c32e665bc8(B2的虚指针)	0x0000000000002222
0x7ffc64cadc30:	0x000000000000dddd(D的数据)	0x000055c32e665bf8(B的虚指针)
0x7ffc64cadc40:	0x00000000000bbbbb	0x0000000000000000
0x7ffc64cadc50:	0x0000000000000000	0x00007ffc64cadc10

看到这应该都清楚了。显然B布局到了最后面。大概就是因为这个所以才能实现只有一份拷贝吧。具体详细分析,自己进去看吧,就是把东西结合起来。
为了方便查看偏移量 我GDB 调试 10进制

(gdb) x /10g pVtab
0x7ffd2687ea70:	94162329979784	4369
0x7ffd2687ea80:	94162329979848	8738
0x7ffd2687ea90:	56797	94162329979896
0x7ffd2687eaa0:	768955	0
0x7ffd2687eab0:	0	140725249895024
(gdb) x /10dg 94162329979784
0x55a3e03acb88 <vtable for D+24>:	94162327877818	94162327877882
0x55a3e03acb98 <vtable for D+40>:	94162327877436	94162327877938
0x55a3e03acba8 <vtable for D+56>:	94162327878000	24(不知道具体怎么排列的,但是知道有就行了)
0x55a3e03acbb8 <vtable for D+72>:	-16	94162329980184
0x55a3e03acbc8 <vtable for D+88>:	94162327877993	94162327877624

(gdb) x /10dg 94162329979848
0x55a3e03acbc8 <vtable for D+88>:	94162327877993	94162327877624
0x55a3e03acbd8 <vtable for D+104>:	0	-40
0x55a3e03acbe8 <vtable for D+120>:	-40	94162329980184
0x55a3e03acbf8 <vtable for D+136>:	94162327877873	94162327877184
0x55a3e03acc08 <VTT for D>:	94162329979784	94162329979992

(gdb) x /10dg 94162329979896
0x55a3e03acbf8 <vtable for D+136>:	94162327877873	94162327877184
0x55a3e03acc08 <VTT for D>:	94162329979784	94162329979992
0x55a3e03acc18 <VTT for D+16>:	94162329980048	94162329980088
0x55a3e03acc28 <VTT for D+32>:	94162329980136	94162329979896
0x55a3e03acc38 <VTT for D+48>:	94162329979848	40

看了好多博客,都搞不懂,自己做出来的和他们有点不一样。
结论你们自己得吧,告辞。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值