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

看了好多博客,都搞不懂,自己做出來的和他們有點不一樣。
結論你們自己得吧,告辭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章