c++派生類對象的內存布局

派生類對象的內存佈局需滿足的要求是,一個基類指針,無論其指向基類對象,還是派生類對象,通過它來訪問一個基類中定義的數據成員,都可以用相同的步驟。

理解了這一要求,就能理解下面介紹的派生類內存佈局的合理性了。對象的內存佈局問題,並不是C+標準中明確規定的,不同的編譯器可以有不同的實現。我介紹一種很多編譯器在使用的、最為自然的和容易理解的對象內存佈局方式。

c++派生類對象的內存佈局

1.單繼承的情況

單繼承的情況比較簡單。考慮下面的情況:

class Base{......};

class Derived: public Base{......};

那麼在 Derived類的對象中, Derived從Base繼承來的數據成員,全部放在前面,與這些數據成員在Base類的對象中放置的順序保持一致, Derived類新增的數據成員全部放在後面,如圖所示。如果出現了從 Derived指針到Base指針的隱含轉換,例如:

Base*Ba=new Base(); //基類指針指向派生類對象

Derived*De=new Derived(); //派生類指針指向派生類對象

Base*Ba2=De; // 派生類指針轉換基類指針

c++派生類對象的內存佈局

單繼承情況下的對象內存佈局

在De賦給Ba2的過程中,指針值不需要改變。Ba和Ba2這兩個Base類型的指針雖然指向的對象具有不同的類型,但任何一個Base數據成員到該對象首地址都具有相同的偏移量,因此使用Base指針Ba和Ba2訪問Base類中定義的數據成員時,可以採用相同的方式,而無須考慮具體的對象類型。

2.多繼承的情況

多繼承的情況要比單繼承稍微複雜一些,考慮下面這種情況:

class Base1{......};

class Base2{......};

class Derived:public Base1,public Base2{...};

Derived類繼承了 Basel類和Base2類,在 Derived類的對象中,前面依次存放的是從Base類和Base2類繼承而來的數據成員,其順序與它們在 Basel類和Basc2類的對象中放置的順序保持一致, Derived類新增的數據放在它們的後面,如圖所示。Base如果出現了從 Derived指針到 Basel指針或Base2指針的隱含轉換,例如:

Base1*Ba1a=new Base1();

Base2*Ba2a=new Base2();

Derived De=new Derived();

Base*Ba1b=De;

Base*Ba2b=De

c++派生類對象的內存佈局

多繼承情況下的對象內存佈局

將pd賦給pblb指針時,與單繼承時的情形相似,只需要把地址複製一遍即可。但將pd 賦給pb2b指針時,則不能簡單地執行地址複製操作,而應當在原地址的基礎上加一個偏 移量,使pb2b指針指向 Derived對象中Base2類的成員的首地址。這樣,對於同為Base2類型指針的pb2a和pb2b來說,它們都指向Base2中定義的、以相同方式分佈的數據成員。

虛擬繼承的情況

虛擬繼承的情況更加複雜,考慮下面的繼承關係

class Base0{...};

class Base1:virtual public Base0{...};

class Base2:virtual public Base0{...};

class Derived:public Base1,public Base2{...};

Basel類型指針和Base2類型指針都可以指向Derived對象,而且通過這兩類指針都可以訪問Base0類中定義的數據成員,但這些數據成員在Derived對象中只有一份。因此,只能通過間接的方式來確定 Basel對象、Base2對象和 Derived對象中Base0數據成員的位置。具體的解決辦法因編譯器而異,一種比較容易理解的佈局方式是,在 Basel類型對象和Base2類型對象中都增加一個隱含的指針,這個指針指向Base0中定義的數據成員的首地址。 Derived類同時繼承了這兩枚指針,但由於 Derived類中的Bac0類數據成員只有一份,因此 Derived類型對象中的這兩個隱含指針指向相同的地址。因為繼承了指向Base0類數據成員的指針,Base0數據成員放置的位置已經不重要了,一般來說可以放在最後。如圖所示:

c++派生類對象的內存佈局

虛擬繼承下的對象內存佈局(只是一種實現方式)

通過上面的討論,我們還得到了一個意外收穫:指針轉換時並非都保持原先的地址不變,地址的算術運算有可能在指針轉換時發生。能看到文章結尾的都是好孩子,感謝大家,喜歡我的文章請關注。


分享到:


相關文章: