C 類物件記憶體模型與成員函式呼叫分析(下)

2021-05-25 06:55:07 字數 3935 閱讀 3844

2.4.2多重繼承下的虛函式

多重繼承下的虛函式主要有一下幾個麻煩:

1.         幾個父類都宣告了相同原型的virtual函式;

2.         有不止乙個父類將其析構函式宣告為虛擬;

3.         一般的虛函式問題;

先給出**段9。

class

parent1

virtual

~parent1()

virtual

void

speakclearly()

virtual

parent1

*clone

()const

protected:

intdata_parent1;

}; class

parent2

virtual

~parent2()

virtual

void

mumble()

virtual

parent2

*clone

()const

protected:

intdata_parent2;

}; class

child 

:public

parent1

,public

parent2

virtual

~child()

virtual

child

*clone

()const

protected:

intdata_child;

}; 就記憶體布局而言,有了前面的基礎了,猜得出來大概是個什麼樣子了。好吧,我們就先猜一把,然後再寫段**驗證驗證。對於資料成員,多重繼承使用的就是各自分配一段空間「疊放」在一起,如之前的圖4所示。對於虛函式,其實就是多了個vptr嘛,也放進去不久結了嗎?

嗯,所以我們可以猜想了,見圖8。

接下來就是除錯驗證了,除錯**段

10如下:

typedef

void

(*fun

)(void);

intmain()

需要的是,這段**的執行要將虛析構函式注釋掉,理由應該很好理解吧,物件都被析構掉了,指標也就成為懸掛指標了,sigsegv就會觸發。code::blocks(gcc 4.5.2)下執行結果如下:

再次說明一下,因為我們注釋掉了虛析構函式那一行,所以上面的輸出中沒有child::~child()之類的資訊。所以,我們的猜想圖8是正確的。

根據《inside the c++ object model》一書,關於多重繼承主要有三種情況要仔細考慮,對著圖8,這三種情況其實都是浮雲。

1.       通過乙個「指向第二個父類,如parent2」的指標,呼叫子類的虛函式。請看**段11:

parent2

*pp2 

=new

child;

// 下面的**將呼叫child::~child()

// 因此pp2必須被向後調整sizeof(parent1)個bytes,由編譯器和執行期資訊參與完成

delete

pp2;

還是回到圖8,注意到乙個問題,parent1和child指標所指向的位置是一樣的(如果都是取的同乙個child物件的位址),但是parent2不是,它與parent1和child的指標之間存在乙個偏移量,看下面的**就知道了:

child c;

parent1

*pp1 =&

c;parent2

*pp2 =&

c;child

*pc =&

c;cout

<<

pp1<<

"/n"

<<

pc<<

"/n"

<<

pp2<<

"/n/n";

執行結果如下圖:

pp1與pc內容一樣,pp2與pp1和pc之間存在8個位元組的偏移量(8個位元組是由乙個4位元組int變數和乙個4位元組指標引起的)。

對於**段11,因為pp2指向child物件中parent2子物件處,為了能夠正確執行,pp2必須調整到child物件起始處。

1.       通過乙個「指向child類」的指標,呼叫parent2中乙個繼承而來的虛函式。在這種情況下,子類指標必須再次被調整,以指向第二個父類parent2處。例如:

child

*pc 

=new

child;

// 呼叫parent2::mumble()

// pc必須被向前調整sizeof(parent1)個bytes,由編譯器和執行期資訊參與完成

pc->

mumble

();

2.       第三種情況發生在乙個語言擴充性質之下:允許乙個虛函式的返回值型別有所變化(注意,返回值型別不是啟用c++過載機制的充分條件),可能是父類型別,也可能是子類型別,這一點通過clone()函式來描述,看下面**:

parent2

*pp1 

=new

child;

// 呼叫child* child::clone()

// 返回值必須被調整,以指向parent2子物件,由編譯器和執行期資訊參與完成

parent2

*pp2 

=pp1

->

clone

();

當進行pp1

->

clone

()時,pp1會被調整到指向child物件的起始位址,於是clone的child版會被呼叫,它會傳回乙個指標,指向乙個新的child物件,該物件的位址在被指定給pp2之前,必須經過調整,以指向parent2子物件處。

之前的注釋中都有一句話,「***必須被調整,以指向parent2子物件,由編譯器和執行期資訊參與完成」,確實,就是編譯器會去做的事情,我們也不用管,因為這也是compiler-dependent的。

2.4.3虛繼承下的虛函式

虛擬繼承的出現就是為了解決重複繼承中多個間接父類的問題的,經典繼承結構圖就是環形繼承鏈:

class

pp ;

class

p1 :

virtual

publicpp;

classp2:

virtual

publicpp;

classc :

publicp1,

publicp2;

1)      先是p1,然後是p2,接著是c,而pp這個超類都放在最後的位置;

2)      各個類內部的布局與多重繼承一樣;

3c++物件模型總結

大道至簡,如果理解了前面的文字,下面四句話應該就差不多了:

l  非靜態資料成員都存放在物件所跨有的位址空間中,靜態資料成員則存放於物件所跨有的位址空間之外;

l  非虛擬成員函式(靜態和非靜態)也存放於物件所跨有的位址空間之外,且編譯器將其改寫為普通的非成員函式的形式(以求降低呼叫開銷);

l  對於虛擬成員函式,則借助vtbl和vptr支援。

l  對於繼承關係,子類物件跨有的位址空間中包含了父類物件的實體,通過嵌入type-info資訊進行識別和虛函式呼叫。

4、參考資料

[1] inside the c++ object model,lippaman,第1、2、4章。

[2] c++物件記憶體布局,陳皓專欄,

C 類物件成員變數與成員函式記憶體分配問題

了解c 類位址的存放和分配等問題,能幫助我們更深入 更清晰了解類的組成及其使用。自己目前不是很清楚,先收集一些網上資料,而後再慢慢補充增加的了解.網路收集之 關於結構體和c 類的記憶體位址問題 今天終於有時間寫點東西了 太爽了 很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構...

C 類物件成員變數與成員函式記憶體分配問題

很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構體只有成員變數 的記憶體分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結構體也也就沒問題啦。類分為成員變數和成員函式,我們先來討論成員變數。乙個類物件的位址就是類所包含的這一片記憶體空間的首位址,這個首位址也就對應...

C 類物件成員變數與成員函式記憶體分配問題

很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構體只有成員變數 的記憶體分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結構體也也就沒問題啦。類分為成員變數和成員函式,我們先來討論成員變數。乙個類物件的位址就是類所包含的這一片記憶體空間的首位址,這個首位址也就對應...