C++类内存分布——深度理解继承与虚函数
1.前言與準備
工欲善其事,必先利其器,我們先用好Visual Studio工具,像下面這樣一步一步來:
? ? ?
先選擇左側(cè)的C/C++->命令行,然后在其他選項這里寫上/d1 reportAllClassLayout,它可以看到所有相關(guān)類的內(nèi)存布局,如果寫上/d1 reportSingleClassLayoutXXX(XXX為類名),則只會打出指定類XXX的內(nèi)存布局。近期的VS版本都支持這樣配置。2.單繼承實驗
下面可以定義一個類,像下面這樣:
class Base {int a;int b; public:void CommonFunction(); };
然后編譯一下,可以看到輸出框里面有這樣的排布:
這里不想花精力在內(nèi)存對齊因素上,所以成員變量都設(shè)為int型。從這里可以看到普通類的排布方式,成員變量依據(jù)聲明的順序進行排列(類內(nèi)偏移為0開始),成員函數(shù)不占內(nèi)存空間。
再看下繼承,往后面添加如下代碼:
class DerivedClass: public Base {int c; public:void DerivedCommonFunction(); };編譯,然后看到如下的內(nèi)存分布(父類的內(nèi)存分布不變,這里只討論子類成員變量的內(nèi)存分布):
可以看到子類繼承了父類的成員變量,在內(nèi)存排布上,先是排布了父類的成員變量,接著排布子類的成員變量,同樣,成員函數(shù)不占字節(jié)。
下面給基類加上虛函數(shù),暫時注釋掉DerivedClass,看一下這時的內(nèi)存排布:class Base {int a;int b; public:void CommonFunction();void virtual VirtualFunction(); };
這個內(nèi)存結(jié)構(gòu)圖分成了兩個部分,上面是內(nèi)存分布,下面是虛表,我們逐個看。VS所帶編譯器是把虛表指針放在了內(nèi)存的開始處(0地址偏移),然后再是成員變量;下面生成了虛表,緊跟在&Base1_meta后面的0表示,這張?zhí)摫韺奶撝羔樤趦?nèi)存中的分布,下面列出了虛函數(shù),左側(cè)的0是這個虛函數(shù)的序號,這里只有一個虛函數(shù),所以只有一項,如果有多個虛函數(shù),會有序號為1,為2的虛函數(shù)列出來。
編譯器是在構(gòu)造函數(shù)創(chuàng)建這個虛表指針以及虛表的。
那么編譯器是如何利用虛表指針與虛表來實現(xiàn)多態(tài)的呢?
是這樣的,當創(chuàng)建一個含有虛函數(shù)的父類的對象時,編譯器在對象構(gòu)造時將虛表指針指向父類的虛函數(shù);同樣,當創(chuàng)建子類的對象時,編譯器在構(gòu)造函數(shù)里將虛表指針(子類只有一個虛表指針,它來自父類)指向子類的虛表(這個虛表里面的虛函數(shù)入口地址是子類的)。
所以,如果是調(diào)用Base *p = new Derived();生成的是子類的對象,在構(gòu)造時,子類對象的虛指針指向的是子類的虛表,接著由Derived*到Base*的轉(zhuǎn)換并沒有改變虛表指針,所以這時候p->VirtualFunction,實際上是p->vfptr->VirtualFunction,它在構(gòu)造的時候就已經(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,注意虛表指針只有一個,子類并沒有再生成虛表指針了;下半部的虛表情況與父類是一樣的。
#######################################################
我們把子類換個代碼,像這樣:
還是只有一個虛表指針,但是下方虛表的內(nèi)容變化了,虛表的0號是父類的VirtualFunction,而1號放的是子類的VirtualFunction2。也就是說,如果定義了DerivedClass的對象,那么在構(gòu)造時,虛表指針就會指向這個虛表,以后如果調(diào)用的是VirtualFunction,那么會從父類中尋找對應的虛函數(shù),如果調(diào)用的是VirtualFunction2,那么會從子類中尋找對應的虛函數(shù)。
3.多繼承實驗
下面來討論多重繼承,代碼如下:
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中有一個虛表指針,地址偏移為0。DerivedClass1繼承了Base,內(nèi)存排布是先父類后子類。
??
DerivedClass2的情況是類似于DerivedClass1的。
下面我們重點看看這個類DerivedDerivedClass,由外向內(nèi)看,它并列地排布著繼承而來的兩個父類DerivedClass1與DerivedClass2,還有自身的成員變量e。DerivedClass1包含了它的成員變量c,以及Base,Base有一個0地址偏移的虛表指針,然后是成員變量a和b;DerivedClass2的內(nèi)存排布類似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。
這里有兩份虛表了,分別針對DerivedClass1與DerivedClass2,在&DerivedDericedClass_meta下方的數(shù)字是首地址偏移量,靠下面的虛表的那個-16表示指向這個虛表的虛指針的內(nèi)存偏移,這正是DerivedClass2中的{vfptr}在DerivedDerivedClass的內(nèi)存偏移。4.總結(jié)
虛繼承的作用是減少了對基類的重復,代價是增加了虛表指針的負擔(更多的虛表指針)。
下面總結(jié)一下(當基類有虛函數(shù)時):
1. 每個類都有虛指針和虛表;
2. 如果不是虛繼承,那么子類將父類的虛指針繼承下來,并指向自身的虛表(發(fā)生在對象構(gòu)造時)。有多少個虛函數(shù),虛表里面的項就會有多少。多重繼承時,可能存在多個的基類虛表與虛指針;
3. 如果是虛繼承,那么子類會有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時虛基表與虛基表指針有且只有一份。
總結(jié)
以上是生活随笔為你收集整理的C++类内存分布——深度理解继承与虚函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 即时通讯浪潮袭人
- 下一篇: C/C++获取高精度时间