解析C++類內(nèi)存分布
工欲善其事,必先利其器,我們先用好Visual Studio工具,像下面這樣一步一步來(lái):


先選擇左側(cè)的C/C++->命令行,然后在其他選項(xiàng)這里寫(xiě)上/d1 reportAllClassLayout,它可以看到所有相關(guān)類的內(nèi)存布局,如果寫(xiě)上/d1 reportSingleClassLayoutXXX(XXX為類名),則只會(huì)打出指定類XXX的內(nèi)存布局。近期的VS版本都支持這樣配置。
下面可以定義一個(gè)類,像下面這樣:
class Base
{
int a;
int b;
public:
void CommonFunction();
};
然后編譯一下,可以看到輸出框里面有這樣的排布:

這里不想花精力在內(nèi)存對(duì)齊因素上,所以成員變量都設(shè)為int型。
從這里可以看到普通類的排布方式,成員變量依據(jù)聲明的順序進(jìn)行排列(類內(nèi)偏移為0開(kāi)始),成員函數(shù)不占內(nèi)存空間。
再看下繼承,往后面添加如下代碼:
class DerivedClass: public Base
{
int c;
public:
void DerivedCommonFunction();
};
編譯,然后看到如下的內(nèi)存分布(父類的內(nèi)存分布不變,這里只討論子類成員變量的內(nèi)存分布):

可以看到子類繼承了父類的成員變量,在內(nèi)存排布上,先是排布了父類的成員變量,接著排布子類的成員變量,同樣,成員函數(shù)不占字節(jié)。
下面給基類加上虛函數(shù),暫時(shí)注釋掉DerivedClass,看一下這時(shí)的內(nèi)存排布:
class Base
{
int a;
int b;
public:
void CommonFunction();
void virtual VirtualFunction();
};

這個(gè)內(nèi)存結(jié)構(gòu)圖分成了兩個(gè)部分,上面是內(nèi)存分布,下面是虛表,我們逐個(gè)看。VS所帶編譯器是把虛表指針?lè)旁诹藘?nèi)存的開(kāi)始處(0地址偏移),然后再是成員變量;下面生成了虛表,緊跟在&Base1_meta后面的0表示,這張?zhí)摫韺?duì)應(yīng)的虛指針在內(nèi)存中的分布,下面列出了虛函數(shù),左側(cè)的0是這個(gè)虛函數(shù)的序號(hào),這里只有一個(gè)虛函數(shù),所以只有一項(xiàng),如果有多個(gè)虛函數(shù),會(huì)有序號(hào)為1,為2的虛函數(shù)列出來(lái)。
編譯器是在構(gòu)造函數(shù)創(chuàng)建這個(gè)虛表指針以及虛表的。
那么編譯器是如何利用虛表指針與虛表來(lái)實(shí)現(xiàn)多態(tài)的呢?是這樣的,當(dāng)創(chuàng)建一個(gè)含有虛函數(shù)的父類的對(duì)象時(shí),編譯器在對(duì)象構(gòu)造時(shí)將虛表指針指向父類的虛函數(shù);同樣,當(dāng)創(chuàng)建子類的對(duì)象時(shí),編譯器在構(gòu)造函數(shù)里將虛表指針(子類只有一個(gè)虛表指針,它來(lái)自父類)指向子類的虛表(這個(gè)虛表里面的虛函數(shù)入口地址是子類的)。
所以,如果是調(diào)用Base *p = new Derived();生成的是子類的對(duì)象,在構(gòu)造時(shí),子類對(duì)象的虛指針指向的是子類的虛表,接著由Derived*到Base*的轉(zhuǎn)換并沒(méi)有改變虛表指針,所以這時(shí)候p->VirtualFunction,實(shí)際上是p->vfptr->VirtualFunction,它在構(gòu)造的時(shí)候就已經(jīng)指向了子類的VirtualFunction,所以調(diào)用的是子類的虛函數(shù),這就是多態(tài)了。
下面加上子類,并在子類中添加虛函數(shù),像下面這樣:
class DerivedClass: public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
可以看到子類內(nèi)存的排布如下:

上半部是內(nèi)存分布,可以看到,虛表指針被繼承了,且仍位于內(nèi)存排布的起始處,下面是父類的成員變量a和b,最后是子類的成員變量c,注意虛表指針只有一個(gè),子類并沒(méi)有再生成虛表指針了;下半部的虛表情況與父類是一樣的。
我們把子類換個(gè)代碼,像這樣:
class DerivedClass1 : public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction2();
};
注意到這時(shí)我們并沒(méi)有覆寫(xiě)父類的虛方法,而是重聲明了一個(gè)新的子類虛方法,內(nèi)存分布如下:

還是只有一個(gè)虛表指針,但是下方虛表的內(nèi)容變化了,虛表的0號(hào)是父類的VirtualFunction,而1號(hào)放的是子類的VirtualFunction2。也就是說(shuō),如果定義了DerivedClass的對(duì)象,那么在構(gòu)造時(shí),虛表指針就會(huì)指向這個(gè)虛表,以后如果調(diào)用的是VirtualFunction,那么會(huì)從父類中尋找對(duì)應(yīng)的虛函數(shù),如果調(diào)用的是VirtualFunction2,那么會(huì)從子類中尋找對(duì)應(yīng)的虛函數(shù)。
我們?cè)俑脑煲幌伦宇悾襁@樣:
class DerivedClass1 : public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
void virtual VirtualFunction2();
};
我們既覆寫(xiě)父類的虛函數(shù),也有新添的虛函數(shù),那么可以料想的到,是下面的這種內(nèi)存分布:

下面來(lái)討論多重繼承,代碼如下:
class Base
{
int a;
int b;
public:
void CommonFunction();
void virtual VirtualFunction();
};
class DerivedClass1: public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedClass2 : public Base
{
int d;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
int e;
public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
};
內(nèi)存分布從父類到子類,依次如下:

Base中有一個(gè)虛表指針,地址偏移為0

DerivedClass1繼承了Base,內(nèi)存排布是先父類后子類。

DerivedClass2的情況是類似于DerivedClass1的。

下面我們重點(diǎn)看看這個(gè)類DerivedDerivedClass,由外向內(nèi)看,它并列地排布著繼承而來(lái)的兩個(gè)父類DerivedClass1與DerivedClass2,還有自身的成員變量e。DerivedClass1包含了它的成員變量c,以及Base,Base有一個(gè)0地址偏移的虛表指針,然后是成員變量a和b;DerivedClass2的內(nèi)存排布類似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。

這里有兩份虛表了,分別針對(duì)DerivedClass1與DerivedClass2,在&DerivedDericedClass_meta下方的數(shù)字是首地址偏移量,靠下面的虛表的那個(gè)-16表示指向這個(gè)虛表的虛指針的內(nèi)存偏移,這正是DerivedClass2中的{vfptr}在DerivedDerivedClass的內(nèi)存偏移。
如果采用虛繼承,像下面這樣:
class DerivedClass1: virtual public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedClass2 : virtual public Base
{
int d;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
int e;
public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
};

Base類沒(méi)有變化,但往下看:

DerivedClass1就已經(jīng)有變化了,原來(lái)是先排虛表指針與Base成員變量,vfptr位于0地址偏移處;但現(xiàn)在有兩個(gè)虛表指針了,一個(gè)是vbptr,另一個(gè)是vfptr。vbptr是這個(gè)DerivedClass1對(duì)應(yīng)的虛表指針,它指向DerivedClass1的虛表vbtable,另一個(gè)vfptr是虛基類表對(duì)應(yīng)的虛指針,它指向vftable。
下面列出了兩張?zhí)摫恚谝粡埍硎莢bptr指向的表,8表示{vbptr}與{vfptr}的偏移;第二張表是vfptr指向的表,-8指明了這張表所對(duì)應(yīng)的虛指針位于內(nèi)存的偏移量。


DerivedClass2的內(nèi)存分布類似于DerivedClass1,同樣會(huì)有兩個(gè)虛指針,分別指向兩張?zhí)摫恚ǖ诙埵翘摶惐恚?/p>

下面來(lái)仔細(xì)看一下DerivedDerivedClass的內(nèi)存分布,這里面有三個(gè)虛指針了,但base卻只有一份。第一張?zhí)摫硎莾?nèi)含DerivedClass1的,20表示它的虛指針{vbptr}離虛基表指針{vfptr}的距離,第二張?zhí)摫硎莾?nèi)含DerivedClass2的,12表示它的虛指針{vbptr}離虛基表指針{vfptr}的距離,最后一張表是虛基表,-20指明了它對(duì)應(yīng)的虛指針{vfptr}在內(nèi)存中的偏移。


虛繼承的作用是減少了對(duì)基類的重復(fù),代價(jià)是增加了虛表指針的負(fù)擔(dān)(更多的虛表指針)。
下面總結(jié)一下(當(dāng)基類有虛函數(shù)時(shí)):
1. 每個(gè)類都有虛指針和虛表;
2. 如果不是虛繼承,那么子類將父類的虛指針繼承下來(lái),并指向自身的虛表(發(fā)生在對(duì)象構(gòu)造時(shí))。有多少個(gè)虛函數(shù),虛表里面的項(xiàng)就會(huì)有多少。多重繼承時(shí),可能存在多個(gè)的基類虛表與虛指針;
3. 如果是虛繼承,那么子類會(huì)有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時(shí)虛基表與虛基表指針有且只有一份。
以上就是解析C++類內(nèi)存分布的詳細(xì)內(nèi)容,更多關(guān)于C++類內(nèi)存分布的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C程序中Ubuntu、stm32的內(nèi)存分配問(wèn)題
這篇文章主要介紹了C程序中Ubuntu、stm32的內(nèi)存分配問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
C++結(jié)構(gòu)體struct和類class區(qū)別詳解
struct和class有什么區(qū)別?最本質(zhì)的一個(gè)區(qū)別就是默認(rèn)的訪問(wèn)控制:默認(rèn)的繼承訪問(wèn)權(quán)限,struct是public的,class是private的。2017-11-11
一文掌握C++ const與constexpr及區(qū)別
C++ 11標(biāo)準(zhǔn)中,const 用于為修飾的變量添加“只讀”屬性而 constexpr關(guān)鍵字則用于指明其后是一個(gè)常量,編譯器在編譯程序時(shí)可以順帶將其結(jié)果計(jì)算出來(lái),而無(wú)需等到程序運(yùn)行階段,這樣的優(yōu)化極大地提高了程序的執(zhí)行效率,本文重點(diǎn)介紹C++ const與constexpr區(qū)別介紹,一起看看吧2024-02-02

