程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++對象內存布局,對象內存布局

c++對象內存布局,對象內存布局

編輯:C++入門知識

c++對象內存布局,對象內存布局


出處:http://www.cnblogs.com/coderkian/ 

在沒有用到虛函數的時候,C++的對象內存布局和c語言的struct是一樣的,這個比較容易理解,本文只對有虛函數的情況作分析,大致可以從以下幾個方面闡述,

1. 單一繼承

2. 多重繼承

3. 虛繼承

下面循序漸進的逐個分析,環境是ubuntu 12.04.3 LTS+gcc4.8.1

 

單一繼承

為了實現運行時多態,虛函數的地址並不能在編譯期決定,需要運行時通過虛函數表查找實際調用的虛函數地址。單一繼承主要要弄明白兩個問題:

1. 虛函數表在哪裡?

2. 基類和派生類的虛函數是按照怎樣的規則組織的?

類結構代碼如下:

復制代碼
class base{
public:
  base(int bv):bval(bv){};
  virtual void base_f(){cout<<"base::f()"<<endl;}
  virtual void base_g(){cout<<"base::g()"<<endl;}
private:
  int bval;
};

class derived: public base{
public:
  derived(int bv, int dv):base(bv), dval(dv){};
  void base_f(){cout<<"derived::f()"<<endl;}
  virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
  int dval;
};
復制代碼

使用以下測試代碼查看基類和派生類的內存空間:

復制代碼
base b(10);
FUN fun;
int **pvtab = (int**)&b;
cout<<"[0]:base->vptr"<<endl;
for(int i=0; i<2; i++){
    fun = (FUN)pvtab[0][i];
    cout<<" "<<i<<" ";
    fun();
}
cout<<" 2 "<<pvtab[0][2]<<endl;
cout<<"[1]:bval "<<(int)pvtab[1]<<endl;
derived d(10, 100);
pvtab = (int**)&d;
cout<<"[0]:derived->vptr "<<endl;
for(int i=0; i<3; i++){
    fun = (FUN)pvtab[0][i];
    cout<<" "<<i<<" ";
    fun();
}
cout<<" 3 "<<pvtab[0][3]<<endl;
cout<<"[1]:bval "<<(int)pvtab[1]<<endl;
cout<<"[2]:dval "<<(int)pvtab[2]<<endl;
復制代碼

運行結果:

[0]:base->vptr

0 base::f()

1 base::g()

2 1919247415

[1]:bval 10

[0]:derived->vptr

0 derived::f()

1 base::g()

2 derived::h()

3 0

[1]:bval 10

[2]:dval 100

用圖表示就是這樣的(本文中屬於同一個類的函數和數據成員我都用同一種顏色表示,以便區分)。

結果可以看出:

1. 虛函數表指針在對象的第一個位置。

2. 成員變量根據其繼承和聲明順序依次排列。

3. 虛函數根據繼承和聲明順序依次放在虛函數表中,派生類中重寫的虛函數在原來位置更新,不會增加新的函數。

4. 派生類虛函數表最後一個位置是NULL,但是基類是一個非空指針(紅色字體顯示),我不明白這個位置在gcc中的作用,有知道的朋友可以告訴我,非常感謝!

 

多重繼承

如果加上多重繼承,情況會有那麼一點點復雜了,多個基類的虛函數如何組織才能使用任意一個基類都可以達到多態效果?

類結構代碼如下:

復制代碼
class base1{
public:
    base1(int bv):bval1(bv){};
    virtual void base_f(){cout<<"base1::f()"<<endl;}
    virtual void base1_g(){cout<<"base1::g()"<<endl;}
private:
    int bval1;
};

class base2{
public:
    base2(int bv):bval2(bv){};
    virtual void base_f(){cout<<"base2::f()"<<endl;}
    virtual void base2_g(){cout<<"base2::g()"<<endl;}
private:
    int bval2;
};

class derived: public base1, public base2{
public:
    derived(int bv1, int bv2,  int dv):base1(bv1),base2(bv2), dval(dv){};
    virtual void base_f(){cout<<"derived::f()"<<endl;}
    virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
    int dval;
};
復制代碼

 

測試代碼查看內存布局:

復制代碼
derived d(10, 100, 1000);
FUN fun;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<<endl;
for(int i=0; i<3; i++){
    fun = (FUN)pvtab[0][i];
    cout<<"  "<<i<<"  ";
    fun();
}
cout<<"  3  "<<pvtab[0][3]<<endl;
cout<<"[1]:bval1  "<<(int)pvtab[1]<<endl;
cout<<"[2]:base2->vptr "<<endl;
for(int i=0; i<2; i++){
    fun = (FUN)pvtab[2][i];
    cout<<"  "<<i<<"  ";
    fun();
}
cout<<"  2  "<<pvtab[2][2]<<endl;
cout<<"[3]:bval2  "<<(int)pvtab[3]<<endl;
cout<<"[4]:dval  "<<(int)pvtab[4]<<endl;
復制代碼

 

運行結果如下:

[0]:base1->vptr

  0  derived::f()

  1  base1::g()

  2  derived::h()

  3  -8

[1]:bval1  10

[2]:base2->vptr

  0  derived::f()

  1  base2::g()

  2  0

[3]:bval2  100

[4]:dval  1000

總結:

1. 每個基類有單獨的虛函數表,子類的虛函數放在第一個基類的虛函數表中。

2. 基類內存空間按基類的聲明順序依次排列

3. 如果多個基類之間有同名虛函數,派生類重寫時會覆蓋所有基類的該函數。這裡插一句,如果沒有覆蓋,不同基類之間的同名函數是會產生二義性的,使用時必須指定哪個類的函數。

4. 第一個基類的虛函數表最後一個位置仍不為空(紅色字體),作用不明。

 

虛繼承

虛繼承需要保證基類在派生類中只有一份,所以內存布局比多重繼承又復雜那麼一點點,我們以最典型的菱形繼承為例子介紹。

類結構代碼如下

復制代碼
class base{
public:
    base(int bv):bval(bv){};
    virtual void base_f(){cout<<"base::f()"<<endl;}
    virtual void base_t(){cout<<"base::t()"<<endl;}
private:
    int bval;
};

class base1:virtual public base{
public:
    base1(int bv, int bv1):base(bv), bval1(bv1){};
    void base_f(){cout<<"base1::f()"<<endl;}
    virtual void base1_g(){cout<<"base1::g()"<<endl;}
    virtual void base1_k(){cout<<"base1::k()"<<endl;}
private:
    int bval1;
};

class base2: virtual public base{
public:
    base2(int bv, int bv2):base(bv), bval2(bv2){};
    void base_f(){cout<<"base2::f()"<<endl;}
    virtual void base2_g(){cout<<"base2::g()"<<endl;}
    virtual void base2_k(){cout<<"base2::k()"<<endl;}
private:
    int bval2;
};

class derived: public base1, public base2{
public:
    derived(int bv, int bv1, int bv2,  int dv):base(bv), base1(bv, bv1),base2(bv, bv2), dval(dv){};
    void base_f(){cout<<"derived::f()"<<endl;}
    void base1_g(){cout<<"derived::base1_g()"<<endl;}
    void base2_g(){cout<<"derived::base2_g()"<<endl;}
    virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
    int dval;
}
復制代碼

 

測試代碼查看內存結構

復制代碼
 derived d(10, 100, 1000, 10000);
    FUN fun = NULL;
    int **pvtab = (int**)&d;
    cout<<"[0]:base1->vptr "<<endl;
    for(int i=0; i<5; i++){
        fun = (FUN)pvtab[0][i];
        cout<<"  "<<i<<"  ";
        fun();
    }
    cout<<"  5  "<<pvtab[0][4]<<endl;
    cout<<"[1]:bval1  "<<(int)pvtab[1]<<endl;

    cout<<"[2]:base2->vptr "<<endl;
    for(int i=0; i<3; i++){
        fun = (FUN)pvtab[2][i];
        cout<<"  "<<i<<"  ";
        fun();
    }
    cout<<"  3  "<<pvtab[2][3]<<endl;
    cout<<"[3]:bval2  "<<(int)pvtab[3]<<endl;
    cout<<"[4]:dval  "<<(int)pvtab[4]<<endl;
    cout<<"[5]:base->vptr "<<endl;
    for(int i=0; i<2; i++){
        fun = (FUN)pvtab[5][i];
        cout<<"  "<<i<<"  ";
        fun();
    }
    cout<<"  2  "<<pvtab[5][2]<<endl;
復制代碼

運行結果:

[0]:base1->vptr

  0  derived::f()

  1  derived::base1_g()

  2  base1::k()

  3  derived::base2_g()

  4  derived::h()

  5  134516512

[1]:bval1  100

[2]:base2->vptr

  0  derived::f()

  1  derived::base2_g()

  2  base2::k()

  3  0

[3]:bval2  1000

[4]:dval  10000

[5]:base->vptr

  0  derived::f()

  1  base::t()

  2  134517004

結果可以看出:

1. 直接非虛基類按聲明順序依次排列,然後是派生類,這個和多重繼承是一樣的。

2. 派生類的虛函數仍然放在第一個直接非虛基類的虛函數表中.

3. 虛基類放在最後,只有一份。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved