多重繼承的記憶體結構分析

2021-07-31 04:43:37 字數 3712 閱讀 4702

1

、普通的多繼承情況

需要用類名加兩個冒號

:: 來說明成員所屬的基類。

**如下:

classa

inta;

intx;

};classb

intb;

intx;

};classc : public a, public b

;int_tmain(int argc, _tchar* argv)

我們從成員變數和成員方法兩方面進行觀察,

進入除錯模式:檢視

c物件的內容:

可以看到乙個

c物件實際上包含乙個

a物件,乙個

b物件,然後再加上自身特有的屬性c,從

a繼承來的x和從

b繼承來的

x 並不在同一塊空間中。但是由於他們的識別符號(變數名)一樣,所以要通過所在的基類名來說明。

注意:關於上圖中的內容只是物件的邏輯模型,而不是真實的記憶體模型

pc->a::fun();

009a2df3  mov        ecx,dword ptr [pc] 

009a2df6  call       a::fun (09a1523h) 

pc->b::fun();

009a2dfb  mov        ecx,dword ptr [pc] 

009a2dfe  add        ecx,8 

009a2e01  call       b::fun (09a152dh) 

關於方法的呼叫也是類似。

關於這種編譯器的識別方式,我們稍作了解即可。重點在於跟後面的程式的實現原理。

2、使用虛基類的多重繼承

在看虛基類的例子之前,我們看一下普通的有共同基類的情況

**如下:

classa

inta;

};classb1 : public a

;classb2 : public a

;classc : public b1, public b2

;int_tmain(int argc, _tchar* argv)

先看一下物件的內容,可以發現,它首先是由包含b1和

b2的物件,然後再b1和

b2 中分別包含

a物件。c佔

20個位元組。

然後再看一下方法的呼叫情況:

pc->b1::func();

000b68f2  mov        ecx,dword ptr [pc] 

000b68f5  call       a::func (0b1537h) 

pc->b2::func();

000b68fa  mov        ecx,dword ptr [pc] 

000b68fd  add        ecx,8 

000b6900  call       a::func (0b1537h) 

呼叫的方法都是

a中的方法,但是在找到

a的過程需要明確說明:是通過b1

物件還是通過

b2物件。

我們再看一下使用虛基類的情況:

classa

inta;

};classb1 : virtual public a

;classb2 : virtual public a

;classc : public b1, public b2

;int_tmain(int argc, _tchar* argv)

首先還是先看一下

c物件的內容

發現它在原來的基礎上又多了乙個公共的基類物件

a,此時

c物件占用的空間為

24個位元組。需要注意的是,圖中所示的成員變數只是邏輯意義上的。我們再來看一下記憶體中的實際資料。

b1、b2虛繼承之後會在物件開始新增兩個虛指標。真正從

a繼承來的部分在最後存放。

然後再看方法的呼叫,

pc->b1::func();

00e76950  mov        eax,dword ptr [pc] 

00e76953  mov        ecx,dword ptr [eax] 

00e76955  mov        edx,dword ptr [pc] 

00e76958  add        edx,dword ptr [ecx+4] 

00e7695b  mov        ecx,edx 

00e7695d  call       a::func (0e71537h) 

pc->func();

00e76962 mov         eax,dword ptr[pc] 

//eax

儲存物件的首位址

00e76965 mov         ecx,dword ptr[eax] 

// 把

eax前

4個位元組內容複製到

ecx

00e76967 mov         edx,dword ptr[pc] 

/ / edx

指向物件首位址

00e7696a add         edx,dword ptr [ecx+4]

// [ecx]

處的內容是

00000000, [ecx+4]

處的內容是

00000014

(普通成員所占用的位元組數),相加之後,

edx指向虛基類的首位址。此時,

[edx] = 6

00e7696d  mov        ecx,edx 

00e7696f  call       a::func (0e71537h) 

虛繼承時,成員函式的呼叫方式不發生改變。

綜上所述,在虛繼承時,生成的物件的前四個位元組儲存乙個位址,我們稱它為虛指標,它指向一段儲存當前物件結構資料的記憶體空間。我們可以稱它為虛表,從而找到真正要讀寫和寫入的變數。在後面看了多型的

記憶體結構之後,會更加清楚。

思考題:

classa

inta;

};classb1 : public a

;classb2 : public a

;classc : virtual public b1, virtual public b2

;int_tmain(int argc, _tchar* argv)

這是我測試時,想到的一種情況。現在我們用之前分析的內容來分析一下

c物件是如何存放的,以及虛表中會有哪些內容。

首先,這裡b1和

b2都是普通的繼承。而

c則虛繼承於b1、

b2,那麼b1、

b2是c的虛基類。

c的物件的前四個位元組是乙個虛指標,它指向乙個虛表,虛表中應該存放兩個虛基類物件相對於

c物件首位址的偏移位址。

c物件內容如下:虛指標

cccccccc

(c 的空間,我們沒有初始化

) 05 00 00 00 01 00 00 00(b1

繼承來的內容)

06 00 00 00 02 00 00 00 (b2

繼承來的內容)他們的偏移位址是8和

16(10h)

所以虛表中的內容應該是

00 00 00 00(前4

個位元組為

0,可能是做驗證用的,具體的以後再分析)

08 00 00 00 0a 00 00 00

我們現在來驗證一下

多重繼承 C 中的多重繼承

多重繼承是c 的一項功能,其中乙個類可以從多個類繼承。繼承類的建構函式以它們繼承的相同順序被呼叫。例如,在以下程式中,在a的建構函式之前呼叫b的建構函式。include using namespace std class a class b class c public b,public a not...

C 多重繼承之記憶體儲存

c 之多重繼承 1.c 中class與struct。在c 裡面,class與struct沒有本質的區別,只是class的預設許可權是private,而struct則是public。這個概念也揭示了一點 class和struct在內部儲存結構上是一致的。所以我們可以利用這一點來 class的實現原理。...

多重繼承虛函式表分析

project100.cpp 此檔案包含 main 函式。程式執行將在此處開始並結束。include pch.h include using namespace std 基類1 class base1 virtual voidg 基類2 class base2 virtual voidi 子類 cl...