C 繼承模型的記憶體布局

2021-08-24 23:04:59 字數 3616 閱讀 8296

對於多繼承情況

考慮示例**

structbase1 ;

structbase2 ;

structderived : base1, base2 ;

有如下記憶體布局

首先出現的是派生類derived類的虛表指標vptr

一直以來vptr都被國人翻譯為虛函式表指標

但是vtbl英文原文是virtual table並非virtual function table

為什麼呢

因為這個表不只是為了虛函式而準備的

一切虛擬化技術都會用這個表 包括 虛繼承 rtti等

所以當類本身與其直接間接基類內都未定義任何虛函式時也是有可能有虛表的

典型就是當前類繼承了乙個虛基類..提醒結束)

其次是第一直接基類的資料成員

然後是第二直接基類的虛表指標vptr

再次是第二直接基類的資料成員

如果還有後繼直接基類 那麼依此類推

也許有朋友會困惑為何偏移0處是derived類的vptr

而不是base1的vptr

因為非虛繼承第一直接基類base1與派生類derived的基位址是一致的

derived的記憶體結構正是從base1開始

而對於base1 *pb1 =newderived;

pb1->polymorphicfunction();

這樣的多型引用

事實上我們也是用的derived的虛表vtbl

因為derived的vtbl和base1的vtbl已經融為一體了

也就是說從derived的vtbl中完全可以查到從base1繼承下來的虛函式

以及覆蓋base1的虛函式

而對於base2來說

事實上圖中所示的base2::vptr並不是指向base2類的vtbl

而是指向derived類的vtbl中的乙個thunk位址

這個所謂的thunk位址又指向一段彙編碼

這段thunk彙編碼負責做兩件事:

1.跳轉到vtbl中正確的虛函式(也就是derived的虛函式)位址所在的記憶體單元

2.修改this指標使其指向derived物件,並傳入上一步檢索到的虛函式中

通過這個thunk策略

編譯器實現了c++的多型性

舉個例子

base2 *pb2 =newderived;

deletepb2;

此時使用pb2呼叫虛析構函式時

通過base2::vptr跳轉到了thunk彙編**

執行thunk後跳轉到vtbl中derived::~derived()所在的槽位

然後把當前指向base2子物件部分的的this指標

新增適當偏移使其指向derived物件的記憶體首位址

然後傳入並呼叫derived::~derived()

從而實現了base2 *pb2和事實物件類derived的動態繫結

由此我們不妨思考一下為何動態繫結需要用指標或引用而不能通過例項物件來實現

已知derived d;

base2 b2 = d;

這裡相當於拷貝了d物件中的base2::vptr+base2::data到新物件中

並且修改原先指向dervied::vtbl的vptr,使其重新指向bae2::vtbl

這可區別大了

原來使用base2 *pb2時,base2::vptr指標仍舊指向dervied::vtbl中(的thunk碼)

而賦值給b2物件後,base2::vptr指向的就是乙個完全不同類的虛表base2::vtbl了

回到正題

最後derived類自己定義的成員會放在所有直接基類成員的後面

但是這個說法是有待商榷的

下面的例子會進一步說明

對於虛繼承+多繼承的套路有些許變招

考慮示例**

structbase ;

structbase1 :virtualbase ;

structbase2 :virtualbase ;

structderived : base1, base2 ;

有如下記憶體布局

此時注意到幾個變化:

1. 虛基類base的記憶體空間位於dervied類成員的下面

也就是位於整個記憶體空間的最後

即使它是所有類的共同祖先

大家不妨進一步思考這樣做的目的

正常來說 非直接基類是不該出現在派生類記憶體中的

但編譯器正是通過這樣的舉措

才得以實現虛基類在所有子子孫孫派生類都共享乙個共同虛基類的語法特性

此時不論是把這個derived物件賦予base1指標還是base2指標

他們都只需要把按照自己既定的慣例加上乙個固定的偏移

就能在物件的尾部訪問到那個唯一的虛基類

2.derived的成員事實上是位於所有非虛

直接基類的後面

位於所有繼承鏈上的虛基類的前面

大家可以看到c++標準委員會和編譯器大廠設計這一套布局的良苦用心

他們並未因為derived::vptr在base1的子物件域中而就盲目地把derived::data也挪過去

而是把data放在了所有非虛直接基類的後面

這樣就保證了其每個非虛直接基類的完整性和模組化

並且方便在動態繫結時計算指標偏移

例如structderived: base1,base2,...,basen

basen *pbn =newderived;

此時只需要令

this+=sizeof(base1+base2+...+base[n-1])即可指向basen的起始位置

非常方便 當然this是個derived * const型別 所以不能被修改

這裡只是意會轉為彙編後的暫存器操作

3.有乙個需要特別注意的地方

就是如果繼承鏈上的某個類既沒有虛指標也沒有資料成員

也就是說它沒有記憶體分配

這個時候就要小心了

其指標將指向derived物件的首位址!

也就是該類的指標將會與pb1、pd儲存同樣的位址

這樣做的原因就是避免它指向位於其指定記憶體區域下方的

乙個與他無關的物件的首位址

C 類繼承記憶體布局

c 類繼承記憶體布局 c 繼承分為兩種,普通繼承和虛擬繼承 virtual 具體的繼承又根據父類中的函式是否virtual而不同。下面就單繼承分為幾種情況闡述 1.普通繼承 父類無virtual函式 若子類沒有新定義virtual函式 此時子類的布局是 由低位址 高位址 為父類的元素 沒有vptr ...

C 物件模型 記憶體布局

聯絡人 石虎暱稱 嗡嘛呢叭咪哄 一 概念 1 沒有繼承情況,vptr存放在物件的開始位置,以下是base1的記憶體布局 m idata 100 2.單繼承的情況下,物件只有乙個vptr,它存放在物件的開始位置,派生類子物件在父類子物件的最後面,以下是d1的記憶體布局 b1 m idata 100 b...

C 物件的記憶體布局 多繼承

多繼承中,乙個派生類可以有多個基類.多繼承是c 頗受爭議的乙個語法特性,它就像一把雙刃劍,在提供便利及強大功能的同時,也帶來了一些容易使人產生錯誤的不便.在此主要說明一下多繼承時物件的記憶體分布 1.與單繼承相同,首先建立基類的物件,但要遵循一定的順序,這個順序是由派生類宣告時決定.2.和單繼承也一...