天天看點

C++類記憶體分布

1 環境搭建

    書上類繼承相關章節到這裡就結束了,這裡不妨說下C++記憶體分布結構,我們來看看編譯器是怎麼處理類成員記憶體分布的,特别是在繼承、虛函數存在的情況下。

工欲善其事,必先利其器,我們先用好Visual Studio工具,像下面這樣一步一步來:

C++類記憶體分布
C++類記憶體分布

    先選擇左側的C/C++->指令行,然後在其他選項這裡寫上/d1 reportAllClassLayout,它可以看到所有相關類的記憶體布局,如果寫上/d1 reportSingleClassLayoutXXX(XXX為類名),則隻會打出指定類XXX的記憶體布局。近期的VS版本都支援這樣配置。

2 單繼承

下面可以定義一個類,像下面這樣:

class Base
{
	int a;
	int b;
	public:
	void CommonFunction();
};
           

然後編譯一下,可以看到輸出框裡面有這樣的排布:

C++類記憶體分布

這裡不想花精力在記憶體對齊因素上,是以成員變量都設為int型。

從這裡可以看到普通類的排布方式,成員變量依據聲明的順序進行排列(類内偏移為0開始),成員函數不占記憶體空間。

再看下繼承,往後面添加如下代碼:

class DerivedClass: public Base
{
	int c;
	
public:
	void DerivedCommonFunction();
};
           

編譯,然後看到如下的記憶體分布(父類的記憶體分布不變,這裡隻讨論子類成員變量的記憶體分布):

C++類記憶體分布

可以看到子類繼承了父類的成員變量,在記憶體排布上,先是排布了父類的成員變量,接着排布子類的成員變量,同樣,成員函數不占位元組。

下面給基類加上虛函數,暫時注釋掉DerivedClass,看一下這時的記憶體排布:

class Base
{
	int a;
	int b;
	
public:
	void CommonFunction();
	void virtual VirtualFunction();
};
           
C++類記憶體分布

  這個記憶體結構圖分成了兩個部分,上面是記憶體分布,下面是虛表,我們逐個看。VS所帶編譯器是把虛表指針放在了記憶體的開始處(0位址偏移),然後再是成員變量;下面生成了虛表,緊跟在&Base1_meta後面的0表示,這張虛表對應的虛指針在記憶體中的分布,下面列出了虛函數,左側的0是這個虛函數的序号,這裡隻有一個虛函數,是以隻有一項,如果有多個虛函數,會有序号為1,為2的虛函數列出來。

    編譯器是在構造函數建立這個虛表指針以及虛表的。

    那麼編譯器是如何利用虛表指針與虛表來實作多态的呢?是這樣的,當建立一個含有虛函數的父類的對象時,編譯器在對象構造時将虛表指針指向父類的虛函數;同樣,當建立子類的對象時,編譯器在構造函數裡将虛表指針(子類隻有一個虛表指針,它來自父類)指向子類的虛表(這個虛表裡面的虛函數入口位址是子類的)。

是以,如果是調用Base *p = new Derived();生成的是子類的對象,在構造時,子類對象的虛指針指向的是子類的虛表,接着由Derived*到Base*的轉換并沒有改變虛表指針,是以這時候p->VirtualFunction,實際上是p->vfptr->VirtualFunction,它在構造的時候就已經指向了子類的VirtualFunction,是以調用的是子類的虛函數,這就是多态了。

下面加上子類,并在子類中添加虛函數,像下面這樣:

class DerivedClass: public Base
{
	int c;
	
public:
	void DerivedCommonFunction();
	void virtual VirtualFunction();
};
           

可以看到子類記憶體的排布如下:

C++類記憶體分布

    上半部是記憶體分布,可以看到,虛表指針被繼承了,且仍位于記憶體排布的起始處,下面是父類的成員變量a和b,最後是子類的成員變量c,注意虛表指針隻有一個,子類并沒有再生成虛表指針了;下半部的虛表情況與父類是一樣的。

我們把子類換個代碼,像這樣:

class DerivedClass1 : public Base
{
	int c;
	
public:

	void DerivedCommonFunction();
	void virtual VirtualFunction2();
};
           

注意到這時我們并沒有覆寫父類的虛方法,而是重聲明了一個新的子類虛方法,記憶體分布如下:

C++類記憶體分布

    還是隻有一個虛表指針,但是下方虛表的内容變化了,虛表的0号是父類的VirtualFunction,而1号放的是子類的VirtualFunction2。也就是說,如果定義了DerivedClass的對象,那麼在構造時,虛表指針就會指向這個虛表,以後如果調用的是VirtualFunction,那麼會從父類中尋找對應的虛函數,如果調用的是VirtualFunction2,那麼會從子類中尋找對應的虛函數。

我們再改造一下子類,像這樣:

class DerivedClass1 : public Base
{
	int c;
	
public:
	void DerivedCommonFunction();
	void virtual VirtualFunction();
	void virtual VirtualFunction2();
};
           

我們既覆寫父類的虛函數,也有新添的虛函數,那麼可以料想的到,是下面的這種記憶體分布:

C++類記憶體分布

2 多繼承

下面來讨論多重繼承,代碼如下:

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();
};



           

記憶體分布從父類到子類,依次如下:

C++類記憶體分布

Base中有一個虛表指針,位址偏移為0

C++類記憶體分布

DerivedClass1繼承了Base,記憶體排布是先父類後子類。

C++類記憶體分布

DerivedClass2的情況是類似于DerivedClass1的。

C++類記憶體分布

下面我們重點看看這個類DerivedDerivedClass,由外向内看,它并列地排布着繼承而來的兩個父類DerivedClass1與DerivedClass2,還有自身的成員變量e。DerivedClass1包含了它的成員變量c,以及Base,Base有一個0位址偏移的虛表指針,然後是成員變量a和b;DerivedClass2的記憶體排布類似于DerivedClass1,注意到DerivedClass2裡面竟然也有一份Base。

C++類記憶體分布

這裡有兩份虛表了,分别針對DerivedClass1與DerivedClass2,在&DerivedDericedClass_meta下方的數字是首位址偏移量,靠下面的虛表的那個-16表示指向這個虛表的虛指針的記憶體偏移,這正是DerivedClass2中的{vfptr}在DerivedDerivedClass的記憶體偏移。

4 虛繼承

如果采用虛繼承,像下面這樣:

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();
};
           
C++類記憶體分布

Base類沒有變化,但往下看:

C++類記憶體分布

DerivedClass1就已經有變化了,原來是先排虛表指針與Base成員變量,vfptr位于0位址偏移處;但現在有兩個虛表指針了,一個是vbptr,另一個是vfptr。vbptr是這個DerivedClass1對應的虛表指針,它指向DerivedClass1的虛表vbtable,另一個vfptr是虛基類表對應的虛指針,它指向vftable。

下面列出了兩張虛表,第一張表是vbptr指向的表,8表示{vbptr}與{vfptr}的偏移;第二張表是vfptr指向的表,-8指明了這張表所對應的虛指針位于記憶體的偏移量。

C++類記憶體分布
C++類記憶體分布

DerivedClass2的記憶體分布類似于DerivedClass1,同樣會有兩個虛指針,分别指向兩張虛表(第二張是虛基類表)。

C++類記憶體分布

下面來仔細看一下DerivedDerivedClass的記憶體分布,這裡面有三個虛指針了,但base卻隻有一份。第一張虛表是内含DerivedClass1的,20表示它的虛指針{vbptr}離虛基表指針{vfptr}的距離,第二張虛表是内含DerivedClass2的,12表示它的虛指針{vbptr}離虛基表指針{vfptr}的距離,最後一張表是虛基表,-20指明了它對應的虛指針{vfptr}在記憶體中的偏移。

C++類記憶體分布
C++類記憶體分布

虛繼承的作用是減少了對基類的重複,代價是增加了虛表指針的負擔(更多的虛表指針)。

下面總結一下(當基類有虛函數時):

1. 每個類都有虛指針和虛表;

2. 如果不是虛繼承,那麼子類将父類的虛指針繼承下來,并指向自身的虛表(發生在對象構造時)。有多少個虛函數,虛表裡面的項就會有多少。多重繼承時,可能存在多個的基類虛表與虛指針;

3. 如果是虛繼承,那麼子類會有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時虛基表與虛基表指針有且隻有一份。

本文轉自:

http://www.cnblogs.com/jerry19880126/p/3616999.html

繼續閱讀