天天看點

對象記憶體布局 (11)

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

代碼如下:

運作結果:

對象記憶體布局 (11)

我們發現在普通繼承的情況下,将派生類對象的指針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的記憶體分布圖:

對象記憶體布局 (11)

保持整個程式其他部分代碼不做任何變動,我們将Child改為從Parent虛繼承,改後的Child代碼如下:

對象記憶體布局 (11)

與上圖相比較

對象記憶體布局 (11)

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

第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的記憶體分布圖:

對象記憶體布局 (11)

為了驗證上述結論,在上面的基礎上,我們将GrandChild改為虛拟繼承Child,修改後的GrandChild代碼如下:

運作程式,得到如下結果:

對象記憶體布局 (11)

與上面兩個圖相比較:

對象記憶體布局 (11)
對象記憶體布局 (11)

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

如果沒有虛拟繼承,當将派生類對象的指針轉換到基類時(即使基類中有虛函數),指針的值不會發生變化;但當一個派生類對象的指針轉換到虛基類指針時(不管兩者之間是否有其他中間類,而且也不管這些中間類是否是派生類的普通基類還是虛基類),指針的值就會發生變化。

 Grandchild的記憶體分布圖:

對象記憶體布局 (11)

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

重要的是第6、7、8行代碼,它們通過偏移值指針找到偏移值,并以此來調整指針的位置,讓目的指針最終指向對象中的基類部分的資料成員。

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

如果沒有虛拟繼承,當将派生類對象的指針轉換到基類時(即使基類中有虛函數),指針的值不會發生變化;但當一個派生類對象的指針轉換到虛基類指針時(不管兩者之間是否有其他中間類,而且也不管這些中間類是否是派生類的普通基類還是虛基類),指針(目的指針)的值就會發生變化,目的指針最終指向對象中的基類部分(或曰基類的執行個體)。

繼續閱讀