天天看點

C++ 對象的記憶體布局(上)前言對象的影響因素知識複習單一的一般繼承多重繼承

1)有成員變量的情況。

2)有重複繼承的情況。

3)有虛拟繼承的情況。

4)有鑽石型虛拟繼承的情況。

簡而言之,我們一個類可能會有如下的影響因素:

1)成員變量

2)虛函數(産生虛函數表)

3)單一繼承(隻繼承于一個類)

4)多重繼承(繼承多個類)

5)重複繼承(繼承的多個父類中其父類有相同的超類)

6)虛拟繼承(使用virtual方式繼承,為了保證繼承後父類的記憶體布局隻會存在一份)

上述的東西通常是C++這門語言在語義方面對對象内部的影響因素,當然,還會有編譯器的影響(比如優化),還有位元組對齊的影響。在這裡我們都不讨論,我們隻讨論C++語言上的影響。

本篇文章着重讨論下述幾個情況下的C++對象的記憶體布局情況。

1)<b>單一的一般繼承</b>(帶成員變量、虛函數、虛函數覆寫)

2)<b>單一的虛拟繼承</b>(帶成員變量、虛函數、虛函數覆寫)

3)<b>多重繼承</b>(帶成員變量、虛函數、虛函數覆寫)

4)<b>重複多重繼承</b>(帶成員變量、虛函數、虛函數覆寫)

5)<b>鑽石型的虛拟多重繼承</b>(帶成員變量、虛函數、虛函數覆寫)

我們的目标就是,讓事情越來越複雜。

我們簡單地複習一下,我們可以通過對象的位址來取得虛函數表的位址,如:

          typedef void(*Fun)(void);

            Base b;

            Fun pFun = NULL;

            cout &lt;&lt; "虛函數表位址:" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;

            cout &lt;&lt; "虛函數表 — 第一個函數位址:" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&amp;b));

            pFun();

我們同樣可以用這種方式來取得整個對象執行個體的記憶體布局。因為這些東西在記憶體中都是連續分布的,我們隻需要使用适當的位址偏移量,我們就可以獲得整個記憶體對象的布局。

本篇文章中的例程或記憶體布局主要使用如下編譯器和系統:

1)Windows XP 和 VC++ 2003

2)Cygwin 和 G++ 3.4.4

下面,我們假設有如下所示的一個繼承關系:

請注意,在這個繼承關系中,父類,子類,孫子類都有自己的一個成員變量。而了類覆寫了父類的f()方法,孫子類覆寫了子類的g_child()及其超類的f()。

我們的源程式如下所示:

class Parent {

public:

    int iparent;

    Parent ():iparent (10) {}

    virtual void f() { cout &lt;&lt; " Parent::f()" &lt;&lt; endl; }

    virtual void g() { cout &lt;&lt; " Parent::g()" &lt;&lt; endl; }

    virtual void h() { cout &lt;&lt; " Parent::h()" &lt;&lt; endl; }

};

class Child : public Parent {

    int ichild;

    Child():ichild(100) {}

    virtual void f() { cout &lt;&lt; "Child::f()" &lt;&lt; endl; }

    virtual void g_child() { cout &lt;&lt; "Child::g_child()" &lt;&lt; endl; }

    virtual void h_child() { cout &lt;&lt; "Child::h_child()" &lt;&lt; endl; }

class GrandChild : public Child{

    int igrandchild;

    GrandChild():igrandchild(1000) {}

    virtual void f() { cout &lt;&lt; "GrandChild::f()" &lt;&lt; endl; }

    virtual void g_child() { cout &lt;&lt; "GrandChild::g_child()" &lt;&lt; endl; }

    virtual void h_grandchild() { cout &lt;&lt; "GrandChild::h_grandchild()" &lt;&lt; endl; }

我們使用以下程式作為測試程式:(下面程式中,我使用了一個int** pVtab 來作為周遊對象記憶體布局的指針,這樣,我就可以友善地像使用數組一樣來周遊所有的成員包括其虛函數表了,在後面的程式中,我也是用這樣的方法的,請不必感到奇怪,)

    typedef void(*Fun)(void);

    GrandChild gc;

    int** pVtab = (int**)&amp;gc;

    cout &lt;&lt; "[0] GrandChild::_vptr-&gt;" &lt;&lt; endl;

    for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){

                pFun = (Fun)pVtab[0][i];

                cout &lt;&lt; "    ["&lt;&lt;i&lt;&lt;"] ";

                pFun();

    }

    cout &lt;&lt; "[1] Parent.iparent = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;

    cout &lt;&lt; "[2] Child.ichild = " &lt;&lt; (int)pVtab[2] &lt;&lt; endl;

    cout &lt;&lt; "[3] GrandChild.igrandchild = " &lt;&lt; (int)pVtab[3] &lt;&lt; endl;

其運作結果如下所示:(在VC++ 2003和G++ 3.4.4下)

[0] GrandChild::_vptr-&gt;

    [0] GrandChild::f()

    [1] Parent::g()

    [2] Parent::h()

    [3] GrandChild::g_child()

    [4] Child::h1()

    [5] GrandChild::h_grandchild()

[1] Parent.iparent = 10

[2] Child.ichild = 100

[3] GrandChild.igrandchild = 1000

使用圖檔表示如下:

可見以下幾個方面:

1)虛函數表在最前面的位置。

2)成員變量根據其繼承和聲明順序依次放在後面。

3)在單一的繼承中,被overwrite的虛函數在虛函數表中得到了更新。

下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系。注意:子類隻overwrite了父類的f()函數,而還有一個是自己的函數(我們這樣做的目的是為了用g1()作為一個标記來标明子類的虛函數表)。而且每個類中都有一個自己的成員變量:

我們的類繼承的源代碼如下所示:父類的成員初始為10,20,30,子類的為100

class Base1 {

    int ibase1;

    Base1():ibase1(10) {}

    virtual void f() { cout &lt;&lt; "Base1::f()" &lt;&lt; endl; }

    virtual void g() { cout &lt;&lt; "Base1::g()" &lt;&lt; endl; }

    virtual void h() { cout &lt;&lt; "Base1::h()" &lt;&lt; endl; }

class Base2 {

    int ibase2;

    Base2():ibase2(20) {}

    virtual void f() { cout &lt;&lt; "Base2::f()" &lt;&lt; endl; }

    virtual void g() { cout &lt;&lt; "Base2::g()" &lt;&lt; endl; }

    virtual void h() { cout &lt;&lt; "Base2::h()" &lt;&lt; endl; }

class Base3 {

    int ibase3;

    Base3():ibase3(30) {}

    virtual void f() { cout &lt;&lt; "Base3::f()" &lt;&lt; endl; }

    virtual void g() { cout &lt;&lt; "Base3::g()" &lt;&lt; endl; }

    virtual void h() { cout &lt;&lt; "Base3::h()" &lt;&lt; endl; }

class Derive : public Base1, public Base2, public Base3 {

    int iderive;

    Derive():iderive(100) {}

    virtual void f() { cout &lt;&lt; "Derive::f()" &lt;&lt; endl; }

    virtual void g1() { cout &lt;&lt; "Derive::g1()" &lt;&lt; endl; }

我們通過下面的程式來檢視子類執行個體的記憶體布局:下面程式中,注意我使用了一個s變量,其中用到了sizof(Base)來找下一個類的偏移量。(因為我聲明的是int成員,是以是4個位元組,是以沒有對齊問題。關于記憶體的對齊問題,大家可以自行試驗,我在這裡就不多說了)

             typedef void(*Fun)(void);

               Derive d;

                int** pVtab = (int**)&amp;d;

                cout &lt;&lt; "[0] Base1::_vptr-&gt;" &lt;&lt; endl;

                pFun = (Fun)pVtab[0][0];

                cout &lt;&lt; "     [0] ";

                pFun = (Fun)pVtab[0][1];

                cout &lt;&lt; "     [1] ";pFun();

                pFun = (Fun)pVtab[0][2];

                cout &lt;&lt; "     [2] ";pFun();

                pFun = (Fun)pVtab[0][3];

                cout &lt;&lt; "     [3] "; pFun();

                pFun = (Fun)pVtab[0][4];

                cout &lt;&lt; "     [4] "; cout&lt;&lt;pFun&lt;&lt;endl;

                cout &lt;&lt; "[1] Base1.ibase1 = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;

<b>                int</b><b> s = sizeof(Base1)/4;</b>

                cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base2::_vptr-&gt;"&lt;&lt;endl;

                pFun = (Fun)pVtab[s][0];

                cout &lt;&lt; "     [0] "; pFun();

                Fun = (Fun)pVtab[s][1];

                cout &lt;&lt; "     [1] "; pFun();

                pFun = (Fun)pVtab[s][2];

                cout &lt;&lt; "     [2] "; pFun();

                pFun = (Fun)pVtab[s][3];

                out &lt;&lt; "     [3] ";

                cout&lt;&lt;pFun&lt;&lt;endl;

                cout &lt;&lt; "["&lt;&lt; s+1 &lt;&lt;"] Base2.ibase2 = " &lt;&lt; (int)pVtab[s+1] &lt;&lt; endl;

                <b>s = s + sizeof(Base2)/4;</b>

                cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base3::_vptr-&gt;"&lt;&lt;endl;

                pFun = (Fun)pVtab[s][1];

                cout &lt;&lt; "     [1] "; pFun();

                 cout &lt;&lt; "     [3] ";

                s++;

                cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Base3.ibase3 = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;

                cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Derive.iderive = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;

[0] Base1::_vptr-&gt;

     [0] Derive::f()

     [1] Base1::g()

     [2] Base1::h()

     [3] Driver::g1()

     [4] 00000000      ç 注意:在GCC下,這裡是1

[1] Base1.ibase1 = 10

[2] Base2::_vptr-&gt;

     [1] Base2::g()

     [2] Base2::h()

     [3] 00000000      ç 注意:在GCC下,這裡是1

[3] Base2.ibase2 = 20

[4] Base3::_vptr-&gt;

     [1] Base3::g()

     [2] Base3::h()

     [3] 00000000

[5] Base3.ibase3 = 30

[6] Derive.iderive = 100

使用圖檔表示是下面這個樣子:

我們可以看到:

1)  每個父類都有自己的虛表。

2)  子類的成員函數被放到了第一個父類的表中。

3)  記憶體布局中,其父類布局依次按聲明順序排列。

4)  每個父類的虛表中的f()函數都被overwrite成了子類的f()。這樣做就是為了解決不同的父類類型的指針指向同一個子類執行個體,而能夠調用到實際的函數。

本文轉自 haoel 51CTO部落格,原文連結:http://blog.51cto.com/haoel/124567,如需轉載請自行聯系原作者

繼續閱讀