淺析GCC下C++多重繼承 & 虛擬繼承的對象內存佈局

     繼承是C++做爲OOD程序設計語言的三大特徵(封裝,繼承,多態)之一,單一非多態繼承是比較好理解的,本文主要講解GCC環境下的多重繼承和虛擬繼承的對象內存佈局。ios

1、多重繼承函數

      先看幾個類的定義:佈局

class Top
 {
public:
      int a;
 };

 class Left : public Top
 {
public:
      int b;
 };

 class Right : public Top
 {
public:
      int c;
 };

 class Bottom : public Left, public Right
 {
public:
      int d;
 };

          不難想象,Left和Right類的內存佈局以下圖所示:

                 

          咱們以下進行驗證:spa

Left *left = new Left();
    Top *top = left;
    cout << left <<  '\t' << top << endl;//輸出:0x902c008       0x902c008
    Right *right = new Right();
    top = right;
    cout << right << '\t' << top << endl;//輸出:0x902c018       0x902c018

         從輸出結果能夠看出,父類指針top指向子類對象left和right的起始地址,與上述內存佈局吻合。

         在非虛擬多重繼承的狀況下,子類的內存佈局是什麼樣子的呢?以下所示:設計

           

          能夠看出,Bottom類因爲繼承了Left和Right,而Left和Right又分別繼承了Top。所以,Bottom包含了Top兩次!指針

          下面進行驗證:code

Bottom *bottom = new Bottom();   
//  top = bottom;     //error: ‘Top’ is an ambiguous base of ‘Bottom’   
top = (Left *)bottom;
left = bottom;
    cout << bottom << '\t' << top << '\t' << left << endl;//輸出:0x9930028 0x9930028 0x9930028
    top = (Right *)bottom;
    right = bottom;
    cout << bottom << '\t' << top << '\t' << right << endl;//輸出:0x9930028 0x9930030 0x9930030
         從輸出結果能夠看出,left指針和right指針分別指向了bottom對象中它們所處的位置:
             

        因爲bottom對象中存在兩部分top對象,所以不能直接用top指針指向bottom對象,由於編譯器不知道你的意圖究竟是指向left中的bottom部分,仍是right中的bottom部分。須要進行轉換才能夠。若是須要經過bottom指針分別訪問left和right中的top部分,能夠以下:  bottom->Left::a,  bottom->Right::a。

         好了,到這裏講完了非虛擬繼承下的多重繼承的內存佈局狀況,相信你們應該有一個比較清晰的認識了。最重要的一點是: 多重繼承時,父類共同繼承的祖父類會在子類中有多份存在。

 

2、虛擬繼承對象

     平時討論的最多的是虛函數,不多涉及到虛擬繼承的狀況。那麼,虛擬繼承究竟是一個什麼概念呢?繼承

      先來看一個例子:    內存

#include <iostream>
using namespace std;

 class Father
 {
public:
    int a;
 };

 class Child : virtual public Father
 {
 public:
    int b;
 };

 int main()
 {
    cout << sizeof(Father) << '\t' << sizeof(Child) << endl;//輸出:4   12
    Child child;
    cout << &child << '\t' << &child.b << '\t' << &child.a << endl;//輸出:0xbfc08124 0xbfc08128 0xbfc0812c
    return 0;
 }

      對,你沒有看錯,類的大小輸出不是4   8,而是4   12。虛擬繼承時,編譯器會在子類中安插上一個虛表指針。

      從輸出的對象成員地址來看,咱們能夠獲得Child類的以下內存佈局:

         

      如今咱們對多重繼承的例子進行改造:

class Top
 {
 public:
      int a;
 };

 class Left : virtual public Top
 {
 public:
      int b;
 };

 class Right : virtual public Top
 {
 public:
     int c;
 };

 class Bottom : public Left, public Right
 {
 public:
     int d;
 };

     把Left和Right改爲了虛擬繼承Top。

      從上面驗證簡單虛擬繼承時,編譯器安插虛表指針的例子,咱們能夠想象出此時Bottom類的對象內存佈局以下:

        

        對,你沒有看錯!虛擬繼承時,子類只有父類共同繼承的祖父類的一份存在。這其實也就是虛擬繼承的最大用途。此時,Top,Left,Right和Bottom對象的大小分別爲:4  ,12  ,12 ,24。

         既然有虛表指針了,那麼Bottom的虛表是什麼樣的呢?請看:

        

        有了虛表,內存佈局狀況一目瞭然。下面咱們進行驗證:

Bottom *bottom = new Bottom();
    top = bottom;
    cout << bottom << '\t' << top << endl;//輸出:0x9fa5028       0x9fa503c
    Left *left = bottom;
    cout << bottom << '\t' << left << endl;//輸出:0x9fa5028       0x9fa5028
    Right *right = bottom;
    cout << bottom << '\t' << right << endl;//輸出:0x9fa5028       0x9fa5030

  根據輸出結果,咱們能夠知道指針的指向狀況:

    

因爲引入了虛指針和虛表,left指針和right指針能夠根據虛表提供的偏移量信息,輕鬆訪問到Top::a。


到此爲止,已經討論清楚了多重繼承和虛擬繼承下的對象內存佈局狀況。總結下:非虛擬多重繼承時,子類會有父類

共同繼承祖父類的多份存在;虛擬繼承時,子類會被安插一個虛擬指針;多重虛擬繼承時,子類只有父類共同繼承祖父類的一

份存在。經過父類的虛擬指針,能夠正確地訪問祖父類中的成員。


參考文獻:

1.http://www.tbdata.org/archives/878

 2. 《深度探索C++對象模型》

相關文章
相關標籤/搜索