物件記憶體布局 11

2021-09-06 19:35:31 字數 4453 閱讀 9609

注意:關於記憶體對齊(memory alignment),請看關於記憶體對齊問題,後面將會用到。

下面我們進行在普通繼承(即非虛繼承)時,派生類的指標轉換到基類指標的情形研究。假定各類之間的關係如下圖:

**如下:

#include using

namespace

std;

class

parent

;class child : public

parent

;class grandchild : public

child

;int main(void

)

執行結果:

我們發現在普通繼承的情況下,將派生類物件的指標upcast為基類指標時,指標的值並不會發生改變。

比如上面輸出中的1和3是一樣的。

child* pc = new child();             ->               pc = 0x3d10e0

parent* pp = (parent*)pc;           ->               pp = 0x3d10e0

還有上面的2和4的輸出也是一樣的:

grandchild* pgc = new grandchild();       ->          pgc = 0x3d10f0

child* pc2 = (child*)pgc;                        ->          pc2 = 0x3d10f0

grandchild的記憶體分布圖:

保持整個程式其他部分**不做任何變動,我們將child改為從parent虛繼承,改後的child**如下:

#include using

namespace

std;

class

parent

;class child : public

virtual

parent

;class grandchild : public

child

;int main(void

)

執行結果:

與上圖相比較

現在來一一比較兩者之間的不同:

第1條,兩者相同;

第2條,兩者相同;

第3條,由0x7d10e0變成了0x7d10e8了,也就是說經過parent* pp = (parent*)pc;後,即由pc = 0x7d10e0得到了pp = 0x7d10e8。很奇怪!

第4條,兩者相同;

第5條,由0x7d10f8變成了0x7d1104了,也就是說經過parent* pp2 = (parent*)pc2;後,即由pc2 = 0x7d10f8得到了pp2 = 0x7d1104。很奇怪!

第6條,由0x7d10f8變成了0x7d1104了,也就是說經過parent* pp3 = (parent*)pgc;後,即由pgc = 0x7d10f8得到了pp3 = 0x7d1104。很奇怪!

為什麼會這樣呢?通過上述分析發現,出現這種指標發生變化的情況,均發生在將child*或者grandchild*轉換到parent*的各行。parent是child的虛基類,child又是grandchild的基類。在上面的第4條中,我們通過child* pc2 = (child*)pgc;,試圖將grandchild*轉換為child*,事實上也轉換成功了,同時指標的值並沒有發生改變。grandchild是普通繼承於child的,而非虛擬繼承,換言之,child不是grandchild的虛基類,所以指標轉換時,目標指標的值和賦給它的值保持一致。通過這樣的分析我們似乎可以得出下面的結論:

當乙個派生類物件的指標轉換到虛基類指標時(不管兩者之間是否有其他中間類,而且也不管這些中間類是否是派生類的普通基類還是虛基類),指標的值就會發生變化

child和grandchild的記憶體分布圖:

為了驗證上述結論,在上面的基礎上,我們將grandchild改為虛擬繼承child,修改後的grandchild**如下:

#include using

namespace

std;

class

parent

;class child : public

virtual

parent

;class grandchild : public

virtual

child

;int main(void

)

執行程式,得到如下結果:

與上面兩個圖相比較:

我們看到,現在第4條也發生了變化。因此原來的結論是成立的。再次總結一下這條非常重要的結論:

如果沒有虛擬繼承,當將派生類物件的指標轉換到基類時(即使基類中有虛函式),指標的值不會發生變化;但當乙個派生類物件的指標轉換到虛基類指標時(不管兩者之間是否有其他中間類,而且也不管這些中間類是否是派生類的普通基類還是虛基類),指標的值就會發生變化。

grandchild的記憶體分布圖:

這個結論對後面的了解含有虛基類的物件記憶體布局有著非同一般的意義。對於這個結論,我們還剩下乙個問題,那就是為什麼會這樣呢? 前面我們可以看到賦值後的指標的值並不等於賦給它的物件位址值。也就是說在這個賦值過程中編譯器進行了額外的工作,即調整了指標的值。我們看看上面程式中parent* pp = (parent*)pc; (向上型別轉換,即up-casting) 這行對應的彙編**(在vc中,進行debug時,按alt 8,即可檢視到彙編**),看看編譯器究竟做了些什麼?

38:       parent* pp = (parent*)pc;

00401691 cmp dword ptr [ebp-10h],0

00401695 jne main+120h (004016a0)

00401697 mov dword ptr [ebp-40h],0

0040169e jmp main+12eh (004016ae)

004016a0 mov eax,dword ptr [ebp-10h]

004016a3 mov ecx,dword ptr [eax] //6

004016a5 mov edx,dword ptr [ebp-10h] //

7004016a8 add edx,dword ptr [ecx+4] //

8004016ab mov dword ptr [ebp-40h],edx

004016ae mov eax,dword ptr [ebp-40h]

004016b1 mov dword ptr [ebp-18h],eax

重要的是第6、7、8行**,它們通過偏移值指標找到偏移值,並以此來調整指標的位置,讓目的指標最終指向物件中的基類部分的資料成員。

至此,我們解釋清楚了上面的問題。因為這部分討論的結果太重要了,我們不妨再次總結如下:

如果沒有虛擬繼承,當將派生類物件的指標轉換到基類時(即使基類中有虛函式),指標的值不會發生變化;但當乙個派生類物件的指標轉換到虛基類指標時(不管兩者之間是否有其他中間類,而且也不管這些中間類是否是派生類的普通基類還是虛基類),指標(目的指標)的值就會發生變化,目的指標最終指向物件中的基類部分(或曰基類的例項)。

物件記憶體布局 1

內容概要 滿足下面2個條件時,1.父類有虛函式,子類無虛函式 即無虛函式重寫或無虛函式覆蓋 2.非虛繼承 類物件之記憶體布局1.base類中有兩個虛函式vfbase 1 vfbase 2 和乙個整形成員變數m base,derived類中有乙個整形成員變數m derived,二者的關係如下 如下 i...

物件記憶體布局 3

內容概要 滿足下面2個條件時,1.父類無虛函式,子類有虛函式 2.非虛繼承 類物件之記憶體布局 前篇 http blog.csdn.net pathuang68 archive 2009 04 23 4101977.aspx 如果將base中的兩個虛函式刪除,情況有會怎麼樣呢?將base中的兩個虛函...

物件記憶體布局 7

前篇 在物件記憶體布局 5 的 中,在derived類中覆蓋base1中宣告的vfbase1 1 其他 不變。修改後的derived的定義如下 class derived public base1,public base2,public base3 inline void vfbase1 1 執行結...