虛基類、虛函數,對象內存分佈

近日研究了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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章