轉(zhuǎn)載自:http://blog.csdn.net/guogangj/archive/2008/01/11/2036785.aspx
提示1:對(duì)“內(nèi)存結(jié)構(gòu)”表示有疑問或不解的,先參考:
http://blog.csdn.net/guogangj/archive/2007/05/25/1625199.aspx,
本文使用的表示方法和VC6的Memory視圖一致,即:左上表示低位。
提示2:下文提到的“類大小”嚴(yán)格上來(lái)說(shuō)是該類經(jīng)過(guò)實(shí)例化的對(duì)象的大小。當(dāng)然了,光研究長(zhǎng)度的話,兩者差別不大,因?yàn)?span style="LINE-HEIGHT: 24px" lang=EN-US>:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的結(jié)果都是一樣的。
一、真空類
長(zhǎng)度:1
內(nèi)存結(jié)構(gòu):
評(píng)注:長(zhǎng)度其實(shí)為0,這個(gè)字節(jié)作為內(nèi)容沒有意義,可能每次都不一樣。
二、空類
class CNull2
{
public:
CNull2(){printf("Construct\n");}
~CNull2(){printf("Desctruct\n");}
void Foo(){printf("Foo\n");}
};
|
長(zhǎng)度:1
內(nèi)存結(jié)構(gòu):
評(píng)注:同真空類差不多,內(nèi)部的成員函數(shù)并不會(huì)影響類大小。
三、簡(jiǎn)單類
class COneMember
{
public:
COneMember(int iValue = 0){m_iOne = iValue;};
private:
int m_iOne;
};
|
長(zhǎng)度:4
內(nèi)存結(jié)構(gòu):
評(píng)注:成員數(shù)據(jù)才影響類大小。
四、簡(jiǎn)單繼承
class CTwoMember:public COneMember
{
private:
int m_iTwo;
};
|
長(zhǎng)度:8
內(nèi)存結(jié)構(gòu):
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
|
評(píng)注:子類成員接在父類成員之后。
五、再繼承
class CThreemember:public CTwoMember
{
public:
CThreemember(int iValue=10) {m_iThree = iValue;};
private:
int m_iThree;
};
|
長(zhǎng)度:12
內(nèi)存結(jié)構(gòu):
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
0A 00 00 00 //m_iThree
|
評(píng)注:孫類成員接在子類之后,再再繼承就依此類推了。
六、多重繼承
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;
};
|
長(zhǎng)度:16
內(nèi)存結(jié)構(gòu):
01 00 00 00 //A
02 00 00 00 //B
03 00 00 00 //C
04 00 00 00 //Complex
|
評(píng)注:也是父類成員先出現(xiàn)在前邊,我想這都足夠好理解。
七、復(fù)雜一些的繼承
不寫代碼了,怕讀者看了眼花,改畫圖。

長(zhǎng)度:32
內(nèi)存結(jié)構(gòu):
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
|
評(píng)注:還是把自己的成員放在最后。
只要沒涉及到“虛”(Virtual),我想沒什么難點(diǎn),不巧的是“虛”正是我們要研究的內(nèi)容。
八、趁熱打鐵,看“虛繼承”
class CTwoMember:virtual public COneMember
{
private:
int m_iTwo;
};
|
長(zhǎng)度:12
內(nèi)存結(jié)構(gòu):
E8 2F 42 00 //指針,指向一個(gè)關(guān)于偏移量的數(shù)組,且稱之虛基類偏移量表指針
CC CC CC CC // m_iTwo
00 00 00 00 // m_iOne(虛基類數(shù)據(jù)成員)
|
評(píng)注:virtual讓長(zhǎng)度增加了4,其實(shí)是多了一個(gè)指針,關(guān)于這個(gè)指針,確實(shí)有些復(fù)雜,別的文章有具體分析,這里就不岔開具體講了,可認(rèn)為它指向一個(gè)關(guān)于虛基類偏移量的數(shù)組,偏移量是關(guān)于虛基類數(shù)據(jù)成員的偏移量。
九、“閉合”虛繼承,看看效果

長(zhǎng)度:24
內(nèi)存結(jié)構(gòu):
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
|
評(píng)注:和預(yù)料中的一樣,虛基類的成員m_iA只出現(xiàn)了一次,而且是在最后邊。當(dāng)然了,更復(fù)雜的情況要比這個(gè)難分析得多,但虛繼承不是我們研究的重點(diǎn),我們只需要知道:虛繼承利用一個(gè)“虛基類偏移量表指針”來(lái)使得虛基類即使被重復(fù)繼承也只會(huì)出現(xiàn)一次。
十、看一下關(guān)于static成員
class CStaticNull
{
public:
CStaticNull(){printf("Construct\n");}
~CStaticNull(){printf("Desctruct\n");}
static void Foo(){printf("Foo\n");}
static int m_iValue;
};
|
長(zhǎng)度:1
內(nèi)存結(jié)構(gòu):(同CNull2)
評(píng)注:可見static成員不會(huì)占用類的大小,static成員的存在區(qū)域?yàn)殪o態(tài)區(qū),可認(rèn)為它們是“全局”的,只是不提供全局的訪問而已,這跟C的static其實(shí)沒什么區(qū)別。
十一、帶一個(gè)虛函數(shù)的空類
class CVirtualNull
{
public:
CVirtualNull(){printf("Construct\n");}
~CVirtualNull(){printf("Desctruct\n");}
virtual void Foo(){printf("Foo\n");}
};
|
長(zhǎng)度:4
內(nèi)存結(jié)構(gòu):
00 31 42 00 //指向虛函數(shù)表的指針(虛函數(shù)表后面簡(jiǎn)稱“虛表”)
00423100:(虛表)
41 10 40 00 //指向虛函數(shù)Foo的指針
00401041:
E9 78 02 00 00 E9 C3 03 … //函數(shù)Foo的內(nèi)容(看不懂)
|
評(píng)注:帶虛函數(shù)的類長(zhǎng)度就增加了4,這個(gè)4其實(shí)就是個(gè)指針,指向虛函數(shù)表的指針,上面這個(gè)例子中虛表只有一個(gè)函數(shù)指針,值就是“0x00401041”,指向的這個(gè)地址就是函數(shù)的入口了。
十二、繼承帶虛函數(shù)的類
class CVirtualDerived : public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
private:
int m_iVD;
};
|
長(zhǎng)度:8
內(nèi)存結(jié)構(gòu):
3C 50 42 00 //虛表指針
FF 00 00 00 //m_iVD
0042503C:(虛表)
23 10 40 00 //指向虛函數(shù)Foo的指針,如果這時(shí)候創(chuàng)建一個(gè)CVirtualNull對(duì)象,會(huì)發(fā)現(xiàn)它的虛表的內(nèi)容跟這個(gè)一樣
|
評(píng)注:由于父類帶了虛函數(shù),子類就算沒有顯式聲明虛函數(shù),虛表還是存在的,虛表存放的位置跟父類不同,但內(nèi)容是同的,也就是對(duì)父類虛表的復(fù)制。
十三、子類有新的虛函數(shù)
class CVirtualDerived: public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
virtual void Foo2(){printf("Foo2\n");};
private:
int m_iVD;
};
|
長(zhǎng)度:8
內(nèi)存結(jié)構(gòu):
24 61 42 00 //虛表指針
FF 00 00 00 //m_iVD
00426124:(虛表)
23 10 40 00
50 10 40 00
|
評(píng)注:虛表還是只有一張,不會(huì)因?yàn)樵黾恿诵碌奶摵瘮?shù)而多出另一張來(lái),新的虛函數(shù)的指針將添加在復(fù)制了的虛表的后面。
十四、當(dāng)純虛函數(shù)(pure function)出現(xiàn)時(shí)
class CPureVirtual
{
virtual void Foo() = 0;
};
class CDerivePV : public CPureVirtual
{
void Foo(){printf("vd: Foo\n");};
};
|
長(zhǎng)度:4(CPureVirtual),4(CDerivePV)
內(nèi)存結(jié)構(gòu):
CPureVirtual:
(不可實(shí)例化)
CDerivePV:
28 50 42 00 //虛表指針
00425028:(虛表)
5A 10 40 00 //指向Foo的函數(shù)指針
|
評(píng)注:帶純虛函數(shù)的類不可實(shí)例化,因此列不出其“內(nèi)存結(jié)構(gòu)”,由其派生類實(shí)現(xiàn)純虛函數(shù)。我們可以看到CDerivePV雖然沒有virtual聲明,但由于其父類帶virtual,所以還是繼承了虛表,如果CDerivePV有子類,還是這個(gè)道理。
十五、虛函數(shù)類的多重繼承
前面提到:(子類的虛表)不會(huì)因?yàn)樵黾恿诵碌奶摵瘮?shù)而多出另一張來(lái),但如果有多重繼承的話情況就不是這樣了。下例中你將看到兩張?zhí)摫怼?span style="LINE-HEIGHT: 24px" lang=EN-US>

大小:24
內(nèi)存結(jié)構(gòu)
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
|
評(píng)注:子類的虛函數(shù)接在第一個(gè)基類的虛函數(shù)表的后面,所以B接在A后面,Complex接在B后面。基類依次出現(xiàn),子類成員接在最后面,所以m_iComplex位于最后面。
本來(lái)還想看看更復(fù)雜些的情況,甚至包括虛繼承和虛函數(shù)同時(shí)出現(xiàn)的多重多層繼承情況,但確實(shí)有些復(fù)雜了,自己還有些找不到規(guī)律,所以準(zhǔn)備之后再補(bǔ)充。