C++對象的內存結構

原文地址:http://blog.csdn.net/guogangj/archive/2008/01/11/2036785.aspx

提示1:對“內存結構”表示有疑問或不解的,先參考:

http://blog.csdn.net/guogangj/archive/2007/05/25/1625199.aspx

本文使用的表示方法和VC6Memory視圖一致,即:左上表示低位。

 

提示2:下文提到的“類大小”嚴格上來說是該類經過實例化的對象的大小。當然了,光研究長度的話,兩者差別不大,因爲:CClassA objAsizeof(CClassA)sizeof(objA)得到的結果都是一樣的。

 

一、真空類

class CNull

{

};

長度:1

內存結構:

??

評註:長度其實爲0,這個字節作爲內容沒有意義,可能每次都不一樣。

 

二、空類

class CNull2

{

public:

    CNull2(){printf("Construct/n");}

    ~CNull2(){printf("Desctruct/n");}

    void Foo(){printf("Foo/n");}

};

長度:1

內存結構:

??

評註:同真空類差不多,內部的成員函數並不會影響類大小。

 

三、簡單類

class COneMember

{

public:

    COneMember(int iValue = 0){m_iOne = iValue;};

private:

    int m_iOne;

};

長度:4

內存結構:

00 00 00 00 //m_iOne

評註:成員數據才影響類大小。

 

四、簡單繼承

class CTwoMember:public COneMember

{

private:

    int m_iTwo;

};

長度:8

內存結構:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

評註:子類成員接在父類成員之後。

 

五、再繼承

class CThreemember:public CTwoMember

{

public:

    CThreemember(int iValue=10) {m_iThree = iValue;};

private:

    int m_iThree;

};

長度:12

內存結構:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

0A 00 00 00 //m_iThree

評註:孫類成員接在子類之後,再再繼承就依此類推了。

 

六、多重繼承

class ClassA

{

public:

    ClassA(int iValue=1){m_iA = iValue;};

private:

    int m_iA;

};

 

class ClassB

{

public:

    ClassB(int iValue=2){m_iB = iValue;};

private:

    int m_iB;

};

 

class ClassC

{

public:

    ClassC(int iValue=3){m_iC = iValue;};

private:

    int m_iC;

};

 

class CComplex :public ClassA, public ClassB, public ClassC

{

public:

    CComplex(int iValue=4){m_iComplex = iValue;};

private:

    int m_iComplex;

};

 

長度:16

內存結構:

01 00 00 00  //A

02 00 00 00  //B

03 00 00 00  //C

04 00 00 00  //Complex

評註:也是父類成員先出現在前邊,我想這都足夠好理解。

 

七、複雜一些的繼承

不寫代碼了,怕讀者看了眼花,改畫圖。

長度:32

內存結構:

01 00 00 00 //A

02 00 00 00 //B

03 00 00 00 //C

04 00 00 00 //Complex

00 00 00 00 //OneMember

CC CC CC CC //TwoMember

0A 00 00 00 //ThreeMember

05 00 00 00 //VeryComplex

評註:還是把自己的成員放在最後。

 

只要沒涉及到“虛”(Virtual),我想沒什麼難點,不巧的是“虛”正是我們要研究的內容。

 

八、趁熱打鐵,看“虛繼承”

class CTwoMember:virtual public COneMember

{

private:

    int m_iTwo;

};

長度:12

內存結構:

E8 2F 42 00 //指針,指向一個關於偏移量的數組,且稱之虛基類偏移量表指針

CC CC CC CC // m_iTwo

00 00 00 00 // m_iOne(虛基類數據成員)

評註:virtual讓長度增加了4,其實是多了一個指針,關於這個指針,確實有些複雜,別的文章有具體分析,這裏就不岔開具體講了,可認爲它指向一個關於虛基類偏移量的數組,偏移量是關於虛基類數據成員的偏移量。

 

九、“閉合”虛繼承,看看效果

長度:24

內存結構:

14 30 42 00 //ClassB的虛基類偏移量表指針

02 00 00 00 //m_iB

C4 2F 42 00 //ClassC的虛基類偏移量表指針

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

01 00 00 00 //m_iA

評註:和預料中的一樣,虛基類的成員m_iA只出現了一次,而且是在最後邊。當然了,更復雜的情況要比這個難分析得多,但虛繼承不是我們研究的重點,我們只需要知道:虛繼承利用一個“虛基類偏移量表指針”來使得虛基類即使被重複繼承也只會出現一次。

 

十、看一下關於static成員

class CStaticNull

{

public:

    CStaticNull(){printf("Construct/n");}

    ~CStaticNull(){printf("Desctruct/n");}

    static void Foo(){printf("Foo/n");}

    static int m_iValue;

};

長度:1

內存結構:(同CNull2

評註:可見static成員不會佔用類的大小,static成員的存在區域爲靜態區,可認爲它們是“全局”的,只是不提供全局的訪問而已,這跟Cstatic其實沒什麼區別。

 

十一、帶一個虛函數的空類

class CVirtualNull

{

public:

    CVirtualNull(){printf("Construct/n");}

    ~CVirtualNull(){printf("Desctruct/n");}

    virtual void Foo(){printf("Foo/n");}

};

長度:4

內存結構:

00 31 42 00 //指向虛函數表的指針(虛函數表後面簡稱“虛表”)

 

00423100:(虛表)

41 10 40 00 //指向虛函數Foo的指針

 

00401041:

E9 78 02 00 00 E9 C3 03  //函數Foo的內容(看不懂)

評註:帶虛函數的類長度就增加了4,這個4其實就是個指針,指向虛函數表的指針,上面這個例子中虛表只有一個函數指針,值就是“0x00401041”,指向的這個地址就是函數的入口了。

 

十二、繼承帶虛函數的類

class CVirtualDerived : public CVirtualNull

{

public:

    CVirtualDerived(){m_iVD=0xFF;};

    ~CVirtualDerived(){};

private:

    int m_iVD;

};

長度:8

內存結構:

3C 50 42 00 //虛表指針

FF 00 00 00 //m_iVD

 

0042503C:(虛表)

23 10 40 00 //指向虛函數Foo的指針,如果這時候創建一個CVirtualNull對象,會發現它的虛表的內容跟這個一樣

評註:由於父類帶了虛函數,子類就算沒有顯式聲明虛函數,虛表還是存在的,虛表存放的位置跟父類不同,但內容是同的,也就是對父類虛表的複製。

 

十三、子類有新的虛函數

class CVirtualDerived: public CVirtualNull

{

public:

    CVirtualDerived(){m_iVD=0xFF;};

    ~CVirtualDerived(){};

    virtual void Foo2(){printf("Foo2/n");};

private:

    int m_iVD;

};

長度:8

內存結構:

24 61 42 00 //虛表指針

FF 00 00 00 //m_iVD

 

00426124:(虛表)

23 10 40 00

50 10 40 00

評註:虛表還是隻有一張,不會因爲增加了新的虛函數而多出另一張來,新的虛函數的指針將添加在複製了的虛表的後面。

 

十四、當純虛函數(pure function)出現時

class CPureVirtual

{

    virtual void Foo() = 0;

};

 

class CDerivePV : public CPureVirtual

{

    void Foo(){printf("vd: Foo/n");};

};

長度:4CPureVirtual),4CDerivePV

內存結構:

CPureVirtual:

(不可實例化)

 

CDerivePV:

28 50 42 00 //虛表指針

 

00425028:(虛表)

5A 10 40 00 //指向Foo的函數指針

評註:帶純虛函數的類不可實例化,因此列不出其“內存結構”,由其派生類實現純虛函數。我們可以看到CDerivePV雖然沒有virtual聲明,但由於其父類帶virtual,所以還是繼承了虛表,如果CDerivePV有子類,還是這個道理。

 

十五、虛函數類的多重繼承

前面提到:(子類的虛表)不會因爲增加了新的虛函數而多出另一張來,但如果有多重繼承的話情況就不是這樣了。下例中你將看到兩張虛表。

大小:24

內存結構

F8 50 42 00 //虛表指針

01 00 00 00 //m_iA

02 00 00 00 //m_iB

E8 50 42 00 //虛表指針

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

 

004250F8:(虛表)

5A 10 40 00 //FooA

55 10 40 00 //FooB

64 10 40 00 //FooComplex

 

004250E8:(虛表)

5F 10 40 00 //FooC

評註:子類的虛函數接在第一個基類的虛函數表的後面,所以B接在A後面,Complex接在B後面。基類依次出現,子類成員接在最後面,所以m_iComplex位於最後面。

 

本來還想看看更復雜些的情況,甚至包括虛繼承和虛函數同時出現的多重多層繼承情況,但確實有些複雜了,自己還有些找不到規律,所以準備之後再補充。

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