這篇文章我要簡單地講解下c++對象的內存布局,雖然已經有很多很好的文章,不過通過實現發現有些地方不同的編譯器還是會有差別的,希望和大家交流。
在沒有用到虛函數的時候,C++的對象內存布局和c語言的struct是一樣的,這個比較容易理解,本文只對有虛函數的情況作分析,大致可以從以下幾個方面闡述,
1. 單一繼承
2. 多重繼承
3. 虛繼承
下面循序漸進的逐個分析,環境是ubuntu 12.04.3 LTS+gcc4.8.1
單一繼承
為了實現運行時多態,虛函數的地址並不能在編譯期決定,需要運行時通過虛函數表查找實際調用的虛函數地址。單一繼承主要要弄明白兩個問題:
1. 虛函數表在哪裡?
2. 基類和派生類的虛函數是按照怎樣的規則組織的?
類結構代碼如下:
( base_f(){cout<<<< base_g(){cout<<<< derived: bv, dv): base_f(){cout<<<< derived_h(){cout<<<<
使用以下測試代碼查看基類和派生類的內存空間:
b( **pvtab = (**)&<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<, = (**)&<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<<<<<()pvtab[]<<endl;
運行結果:
[0]:base->vptr
0 base::f()
1 base::g()
[1]:bval 10
[0]:derived->vptr
0 derived::f()
1 base::g()
2 derived::h()
[1]:bval 10
[2]:dval 100
用圖表示就是這樣的(本文中屬於同一個類的函數和數據成員我都用同一種顏色表示,以便區分)。
結果可以看出:
1. 虛函數表指針在對象的第一個位置。
2. 成員變量根據其繼承和聲明順序依次排列。
3. 虛函數根據繼承和聲明順序依次放在虛函數表中,派生類中重寫的虛函數在原來位置更新,不會增加新的函數。
4. 派生類虛函數表最後一個位置是NULL,但是基類是一個非空指針(),我不明白這個位置在gcc中的作用,有知道的朋友可以告訴我,非常感謝!
多重繼承
如果加上多重繼承,情況會有那麼一點點復雜了,多個基類的虛函數如何組織才能使用任意一個基類都可以達到多態效果?
類結構代碼如下:
base_f(){cout<<<< base1_g(){cout<<<< base_f(){cout<<<< base2_g(){cout<<<< derived: base1, bv1, bv2, base_f(){cout<<<< derived_h(){cout<<<<
測試代碼查看內存布局:
derived d(, , **pvtab = (**)&<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<<<<<()pvtab[]<<endl;
運行結果如下:
[0]:base1->vptr
0 derived::f()
1 base1::g()
2 derived::h()
[1]:bval1 10
[2]:base2->vptr
0 derived::f()
1 base2::g()
[3]:bval2 100
[4]:dval 1000
總結:
1. 每個基類有單獨的虛函數表,子類的虛函數放在第一個基類的虛函數表中。
2. 基類內存空間按基類的聲明順序依次排列
3. 如果多個基類之間有同名虛函數,派生類重寫時會覆蓋所有基類的該函數。這裡插一句,如果沒有覆蓋,不同基類之間的同名函數是會產生二義性的,使用時必須指定哪個類的函數。
4. 第一個基類的虛函數表最後一個位置仍不為空(),作用不明。
虛繼承
虛繼承需要保證基類在派生類中只有一份,所以內存布局比多重繼承又復雜那麼一點點,我們以最典型的菱形繼承為例子介紹。
類結構代碼如下
( base_f(){cout<<<< base_t(){cout<<<< base1: bv, bv1): base_f(){cout<<<< base1_g(){cout<<<< base1_k(){cout<<<< base2: bv, bv2): base_f(){cout<<<< base2_g(){cout<<<< base2_k(){cout<<<< derived: base1, bv, bv1, bv2, dv): base_f(){cout<<<< base1_g(){cout<<<< base2_g(){cout<<<< derived_h(){cout<<<<
測試代碼查看內存結構
derived d(, , , = **pvtab = (**)&<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<<<<<()pvtab[]<<<<<<()pvtab[]<<<<<<( i=; i<; i++= (FUN)pvtab[<< <<i<< <<<<pvtab[][]<<endl;
運行結果:
[0]:base1->vptr
0 derived::f()
1 derived::base1_g()
2 base1::k()
3 derived::base2_g()
4 derived::h()
[1]:bval1 100
[2]:base2->vptr
0 derived::f()
1 derived::base2_g()
2 base2::k()
[3]:bval2 1000
[4]:dval 10000
[5]:base->vptr
0 derived::f()
1 base::t()
結果可以看出:
1. 直接非虛基類按聲明順序依次排列,然後是派生類,這個和多重繼承是一樣的。
2. 派生類的虛函數仍然放在第一個直接非虛基類的虛函數表中.
3. 虛基類放在最後,只有一份。