近日研究了vc編譯器下虛函數、虛基類對象的內存佈局,看到一篇比較好的文章,現轉載如下,供大家分享。
鏈接:http://blog.csdn.net/northhero/archive/2009/09/06/4526069.aspx
非虛擬繼承:
在派生類對象裏,按照繼承聲明順序依次分佈基類對象,最後是派生類數據成員。
若基類聲明瞭虛函數,則基類對象頭部有一個虛函數表指針,然後是基類數據成員。
在基類虛函數表中,依次是基類的虛函數,若某個函數被派生類override,則替換爲派生類的函數。
派生類獨有的虛函數被加在第一個基類的虛函數表後面。
虛擬繼承:
在派生類對象裏,按照繼承聲明順序依次分佈非虛基類對象,然後是派生類數據成員,最後是虛基類對象。
若基類聲明瞭虛函數,則基類對象頭部有一個虛函數表指針,然後是基類數據成員。
在基類虛函數表中,依次是基類的虛函數,若某個函數被派生類override,則替換爲派生類的函數。
若直接從虛基類派生的類沒有非虛父類,且聲明瞭新的虛函數,則該派生類有自己的虛函數表,在該派生類頭部;否則派生類獨有的虛函數被加在第一個非虛基類的虛函數表後面。
直接從虛基類派生的類內部還有一個虛基類表指針,在數據成員之前,非虛基類對象之後(若有的話)。
虛基類表中第一個值是該派生類起始地址到該表的偏移;之後的值依次是該派生類的虛基類到該表位置的地址偏移。
非虛擬單繼承
class Base
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Derived : public Base
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(12):
1> +---
1> | +--- (base class Base)
1> 0 | | {vfptr}
1> 4 | | iBase
1> | +---
1> 8 | iDerived
1> +---
1>Derived::$vftable@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func5
非虛擬多繼承
class Base
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Base1
{
public :
virtual void Func1() {cout << " Base1 Func1 called " << endl;} ;
virtual void Func2() {cout << " Base1 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base1 Func3 called " << endl;} ;
int iBase1;
} ;
class Base2
{
public :
virtual void Func2() {cout << " Base2 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base2 Func3 called " << endl;} ;
virtual void Func4() {cout << " Base2 Func4 called " << endl;} ;
int iBase2;
} ;
class Derived : public Base, public Base1, public Base2
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(28):
1> +---
1> | +--- (base class Base)
1> 0 | | {vfptr}
1> 4 | | iBase
1> | +---
1> | +--- (base class Base1)
1> 8 | | {vfptr}
1>12 | | iBase1
1> | +---
1> | +--- (base class Base2)
1>16 | | {vfptr}
1>20 | | iBase2
1> | +---
1>24 | iDerived
1> +---
1>Derived::$vftable@Base@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func5
1>Derived::$vftable@Base1@:
1> | -8
1> 0 | &Base1::Func1
1> 1 | &thunk: this-=8; goto Derived::Func2
1> 2 | &Derived::Func3
1>Derived::$vftable@Base2@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::Func2
1> 1 | &thunk: this-=8; goto Derived::Func3
1> 2 | &Base2::Func4
虛擬單繼承
class Base
{
public :
virtual void Func2() {} ;
int BaseValue;
} ;
class Derived : virtual public Base
{
public :
void Func2() {} ;
virtual void Func4() {} ;
int DerivedValue;
} ;
1>class Derived size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | DerivedValue
1> +---
1> +--- (virtual base Base)
1>12 | {vfptr}
1>16 | BaseValue
1> +---
1>Derived::$vftable@Derived@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func4
1>Derived::$vbtable@:
1> 0 | -4
1> 1 | 8 (Derivedd(Derived+4)Base)
1>Derived::$vftable@Base@:
1> | -12
1> 0 | &Derived::Func2
虛擬多繼承
class Base
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Base1
{
public :
virtual void Func1() {cout << " Base1 Func1 called " << endl;} ;
virtual void Func2() {cout << " Base1 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base1 Func3 called " << endl;} ;
int iBase1;
} ;
class Base2
{
public :
virtual void Func2() {cout << " Base2 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base2 Func3 called " << endl;} ;
virtual void Func4() {cout << " Base2 Func4 called " << endl;} ;
int iBase2;
} ;
class Derived : public virtual Base, public virtual Base1, public Base2
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(32):
1> +---
1> | +--- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | iBase2
1> | +---
1> 8 | {vbptr}
1>12 | iDerived
1> +---
1> +--- (virtual base Base)
1>16 | {vfptr}
1>20 | iBase
1> +---
1> +--- (virtual base Base1)
1>24 | {vfptr}
1>28 | iBase1
1> +---
1>Derived::$vftable@Base2@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func3
1> 2 | &Base2::Func4
1> 3 | &Derived::Func5
1>Derived::$vbtable@:
1> 0 | -8
1> 1 | 8 (Derivedd(Derived+8)Base)
1> 2 | 16 (Derivedd(Derived+8)Base1)
1>Derived::$vftable@Base@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::Func2
1>Derived::$vftable@Base1@:
1> | -24
1> 0 | &Base1::Func1
1> 1 | &thunk: this-=24; goto Derived::Func2
1> 2 | &thunk: this-=24; goto Derived::Func3
菱形虛擬繼承
#include < iostream >
using namespace std;
class Top
{
public :
virtual void Func1() {} ;
int TopValue;
} ;
class Left: virtual public Top
{
public :
void Func1() {} ;
virtual void Func2() {}
int LeftValue;
} ;
class Right: virtual public Top
{
public :
void Func1() {} ;
int RightValue;
} ;
class Derived : public Left, public Right
{
public :
void Func1() {} ;
void Func3() {}
virtual void Func4() {} ;
int DerivedValue;
} ;
int main()
{
Derived d;
Derived* pd = &d;
Top* pt = &d;
cout<<( int *)pt<<endl;
cout<<( int *)pd<<endl;
return 0 ;
}
1>class Derived size(32):
1> +---
1> | +--- (base class Left)
1> 0 | | {vfptr}
1> 4 | | {vbptr}
1> 8 | | LeftValue
1> | +---
1> | +--- (base class Right)
1>12 | | {vbptr}
1>16 | | RightValue
1> | +---
1>20 | DerivedValue
1> +---
1> +--- (virtual base Top)
1>24 | {vfptr}
1>28 | TopValue
1> +---
1>Derived::$vftable@Left@:
1> | &Derived_meta
1> | 0
1> 0 | &Left::Func2
1> 1 | &Derived::Func4
1>Derived::$vbtable@Left@:
1> 0 | -4
1> 1 | 20 (Derivedd(Left+4)Top)
1>Derived::$vbtable@Right@:
1> 0 | 0
1> 1 | 12 (Derivedd(Right+0)Top)
1>Derived::$vftable@Top@:
1> | -24
1> 0 | &Derived::Func1
如上代碼的輸出結果表示虛基類指針的地址比派生類指針地址多了24,符合上面列出的對象內存佈局。
當通過對象訪問基類成員時,無論是否爲虛擬繼承,都可以再編譯時期獲得對象內存佈局,直接計算出偏移量訪問。
當通過指針訪問基類成員時,如果是非虛擬繼承,直接計算偏移量訪問;
如果是虛擬繼承,需要先計算出虛基類表指針的偏移地址,再根據指針找到虛基類表,取虛基類表中該項的內容,根據此內容計算虛基類對象的偏移地址,再根據成員在虛基類對象中的偏移地址訪問。
所以一般說來,當從派生類中訪問虛基類成員時,應該先強制轉化派生類指針爲虛基類指針,然後一直使用虛基類指針來訪問虛基類成員變量。這樣做,可以避免每次都要計算虛基類地址的開銷。
虛函數表不一定在派生類對象的首地址,例如當派生類從基類虛擬繼承時,派生類如果沒有定義新的虛函數,那麼在派生類的對象空間中,首先分佈的是虛基類表,然後是派生類成員,最後是虛基類對象,虛函數表在虛基類對象開始位置。
class Base
{
public :
virtual void fun() {}
} ;
class Derived: virtual public Base
{
void fun() {}
int i;
} ;
1>class Derived size(12):
1> +---
1> 0 | {vbptr}
1> 4 | i
1> +---
1> +--- (virtual base Base)
1> 8 | {vfptr}
1> +---
1>Derived::$vbtable@:
1> 0 | 0
1> 1 | 8 (Derivedd(Derived+0)Base)
1>Derived::$vftable@:
1> | -8
1> 0 | &Derived::fun