C 虛擬繼承多出來的空間問題

2021-10-06 09:02:53 字數 3206 閱讀 1487

虛擬繼承在繼承許可權前加上virtual關鍵字即可構成虛擬繼承,如:

我們可以深入了解一下虛擬繼承的物件模型;如現有乙個基類b,含有乙個公有型別的整形成員_b,派生類d公有繼承基類b,且派生類d含有自己的乙個整形資料成員_d,我們寫乙個測試函式來研究一下派生類d的物件模型:

通過編譯器單步除錯,可以先看一下基類b和派生類d的空間大小,在檢視結果之前,我們可以先用自己目前所了解的知識分析一下他們的空間大小是多少,派生類d單繼承基類b,基類b只有乙個int型別的成員_b,所以基類b的空間大小應該是4位元組,根據單繼承的特點派生類d先拷貝基類b的記憶體空間,在加上自己的記憶體空間,即派生類d空間大小應該是基類的空間大小4位元組加上b的int型_d資料成員的大小4位元組,共8位元組,因此我們得到的結果是:基類是4位元組,派生類是8位元組。

好了,讓我們看一下執行結果:

這個結果貌似跟我們預想的並不一樣,對於基類b是4個位元組毫無疑問,可是派生類d為什麼多4個位元組,且多的4位元組存放的是什麼呢?

我們可以通過檢視彙編**了解一下編譯器在後面到做了什麼:

我們可以看到在定義基類物件時並沒有彙編**,說明這裡僅僅是進行了宣告,並沒有進行什麼具體的步驟,而在定義派生類物件b時,會發現多了三條彙編**:第一步將1壓棧,第二步取派生類物件_d的位址放到ecx暫存器中,第三步呼叫了派生類d的建構函式d:?()。可是在這裡我們並沒有給出派生類d的建構函式,那麼為什麼編譯器會呼叫呢?

這裡就涉及到編譯器會自動合成建構函式的第三種情況:虛擬繼承時派生類會自動合成建構函式。我們再來總結一下編譯器會自動合成建構函式的三種情況:

當乙個類包含另外乙個類的物件,且該物件有自己的預設建構函式。(含子類情況)

當乙個類繼承另外乙個類,且基類有預設的建構函式。(派生類情況)

當乙個類虛擬繼承另外乙個類,無論基類是否有預設的建構函式,編譯器都會自動合成派生類的建構函式。(虛擬繼承情況)

回到我們的主題,那麼這多出來的這三步,到底做了什麼,我們接著執行,來看一下系統生成的派生類建構函式(d:?())有哪些操作:

① 號彙編語句mov dword ptr [this],ecx,表示將暫存器ecx的值賦值給this指標(直接定址),此時這裡的ecx裝的就是派生類物件_d的位址;即之前的三句彙編**中第二句彙編**所做的操作,將_d的位址放到ecx暫存器中。因此,使得this指標指向當前物件_d。

② 號彙編語句cmp dword ptr [ebp+8],0,通過暫存器間接定址,將ebp堆疊基指標向下偏移8個位元組,取其空間雙字位元組大小(dword ptr)的內容,於0進行比較,ebp位址如下:

其向下偏移8位元組後,所取到的內容為1,即有之前的那三句彙編語句中的第一句,將1壓棧得到。

③ 號彙編語句je d::d+32h (0fa17f2h),表示上面的比較成立,即進行本次步驟

④ 號彙編語句mov eax,dword ptr [this],通過直接定址將this位址空間內容前4個位元組放到eax暫存器當中,即將物件_d的位址放到eax中。

⑤ 號彙編語句mov dword ptr [eax],offset d::`vbtable』 (0fa7b30h),將位址0x00fa7b30放到eax暫存器間接定址(位址偏移eax內容的空間)後位址空間的前4個位元組中,因為eax存放的是_d的位址,即將0x0fa7b30放到物件空間的前4個位元組當中。

0x00fa7b30所指向的空間內容。

此時this指標所指向空間的內容(即物件_d空間內容)的前4個位元組為0x00fa7b30。

⑥ 號彙編語句mov eax,dword ptr [this] ,最後通過直接定址將this空間的內容,放到eax暫存器當中。

此時,派生類的建構函式d:?()的基本彙編**已經講解完畢,同時將上面三條彙編語句的作用也詳細的講解了。整個過程大概來講,就是編譯器給派生類自動生成乙個建構函式,並且在生成派生類物件,呼叫建構函式的同時,將1作為引數傳給建構函式,在建構函式內部,將乙個指標放在了物件記憶體空間的前4個位元組。因此派生類多出來的4個位元組就是該指標。

至於為什麼多出來4個位元組,我們已經有所了解,即多出來的4個直接又來存放指標,現在我們再來深入**一下這個指標,所指空間的內容。

在上面可以看到指標0x00fa7b30所指向空間前4個位元組的內容為0,後4個位元組的內容為8,對於他們的作用,接著來執行的我們的**進行測試,通過彙編來檢視編譯器底層的執行過程來看看賦值情況是怎樣的:

對於派生類成員_d通過直接取值的方式進行賦值,而然對於從基類繼承下來的_b的賦值,通過直接定址先將物件d的前4個位元組,存放到暫存器eax中,即取到那個多出來的未知指標,然後通過暫存器間接取值得到eax偏移4位元組後所指空間的值,即得到未知指標向後偏移4位元組所指的內容8,然後將8存放到exc暫存器中,第三步將要賦的值2賦值給d偏移ecx內容後所指向的位址空間。在此完成d._b = 2;的賦值。可以看出該指標像是乙個存放偏移量的指標。

來檢視一下此時派生類物件的物件模型(由於多次重新執行,導致物件的位址發生改變,但這並不會影響我們的結果):

可以看到物件當中首先是乙個指標,通過上面我們可以得到,這是乙個偏移量指標,第乙個4位元組內容存放的是派生類物件的位址偏移量,第二個4位元組內容存放的是派生類當中基類物件的偏移量;因此該物件的第二個位址內容裝的派生類自己的資料成員_d,而第三個位址存放的是從基類繼承下來的資料成員_b。

最後,我們可以得到虛擬繼承的物件模型:

對於虛擬繼承的物件模型我們已經討論清楚了,這裡要注意的是虛擬繼承看似單繼承,但是與單繼承完全不一樣,因為虛擬繼承的特點,派生類多了4位元組的偏移量指標,並且與單繼承不同的點是,在單繼承中從基類繼承下來的資料成員存放在低位址,而在虛擬繼承中從基類繼承下來的資料成員存放在到位址處。

部分摘自於

C 中的虛擬繼承

1.為什麼要引入虛擬繼承 虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如 類d繼承自類b1 b2,而類b1 b2都繼承自類a,因此在類d中兩次出現類a中的變數和函式。為了節省記憶體空間,可以將b1 b2對a的繼承定義為虛擬繼承,而a就成了虛擬基類。實現的 如下 class a ...

C 類的多重繼承與虛擬繼承

摘自 http tech.ddvip.com 2006 12 116512057312798.html 在過去的學習中,我們始終接觸的單個類的繼承,但是在現實生活中,一些新事物往往會擁有兩個或者兩個以上事物的屬性,為了解決這個問題,c 引入了多重繼承的概念,c 允許為乙個派生類指定多個基類,這樣的繼...

C 類的多重繼承與虛擬繼承

在過去的學習中,始終接觸的單個類的繼承,但是在現實生活中,一些新事物往往會擁有兩個或者兩個以上事物的屬性,為了解決這個問題,c 引入了多重繼承的概念,c 允許為乙個派生類指定多個基類,這樣的繼承結構被稱做多重繼承。舉個例子,交通工具類可以派生出汽車和船連個子類,但擁有汽車和船共同特性水陸兩用汽車就必...