從編譯器的輔助信息看c++對象內(nèi)存布局

  • 編程
  • cpp

預知識

本文的內(nèi)容使用的是32位的編譯器編譯出的結(jié)果亏推,可以打印出類的內(nèi)存布局信息

DevCPP IDE

這個IDE是我比較喜歡的windows下的cpp的IDE之一汇荐,它有一個工具->編譯選項他爸,可以選擇編譯器類型,也可以在編譯選項中加入一些信息,為了能夠輸出內(nèi)存布局信息瓜饥,我在編譯時加入以下命令

--std=c++11  -fdump-class-hierarchy 

-fdump-class-hierarchy 這個選項能夠在gcc編譯時生成類的布局信息,生成的文件名類似為6.cpp.002t.class

vs2017

vs使用的編譯器是cl浴骂,它的命令為
/d1reportAllClassLayout: 輸出所有類相關布局

clang

clang -Xclang -fdump-record-layouts

理解內(nèi)存布局信息

測試代碼如下

class father{
    int a;
    int b;
};

class child: public father{
};

int main(){
    return 0;
}

使用 g++ -fdump-class-hierarchy test.cpp 生成了 test.cpp.002t.class內(nèi)容如下

Class father
   size=8 align=4
   base size=8 base align=4
father (0x0x16662d8) 0

Class child
   size=8 align=4
   base size=8 base align=4
child (0x0x3984e00) 0
  father (0x0x1666310) 0

顯示了類的大小和對齊信息乓土。

題話外: 理解內(nèi)存對齊

  1. 結(jié)構(gòu)體第一個成員的偏移量(offset)為0,以后每個成員相對于結(jié)構(gòu)體首地址的 offset 都是該成員大小與有效對齊值中較小那個的整數(shù)倍溯警,如有需要編譯器會在成員之間加上填充字節(jié)趣苏。
  2. 結(jié)構(gòu)體的總大小為 有效對齊值 的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)梯轻。

多態(tài)與虛表

多態(tài)食磕,簡單來說,是指在繼承層次中喳挑,父類的指針可以具有多種形態(tài)——當它指向某個子類對象時彬伦,通過它能夠調(diào)用到子類的函數(shù),而非父類的函數(shù)伊诵。

虛函數(shù)指針一般都放在對象內(nèi)存布局的第一個位置上媚朦,這是為了保證在多層繼承或多重繼承的情況下能以最高效率取到虛函數(shù)表。當vprt位于對象內(nèi)存最前面時日戈,對象的地址即為虛函數(shù)指針地址询张。我們可以取得虛函數(shù)指針的地址

下面的方式取得虛函數(shù)指針的地址(而非虛函數(shù)指針指向的地址)

Base b(1000);
int * vptrAdree = (int *)(&b);

vptrAdree指向了虛函數(shù)指針(指向虛函數(shù)表)浙炼,

而我們可以通過如下的方式獲得第一個虛函數(shù)的地址

Base b(1000);
using fun=void(*) ();
fun fun1 = (fun)*((int *)*(int *)(&b));
fun fun2 = (fun)*((int *)*(int *)(&c)+1);

簡單的情況份氧,單層繼承與虛函數(shù)全覆蓋

通過查看編譯器生成的內(nèi)存布局信息來具體地看:

#include <iostream>
using namespace std;

class Base{
    int a;
    virtual int print(){
        cout<<" i am base"<<endl;
    }
    virtual int print2(){
        cout<<" i am base2" <<endl;
    }
}; 

class child: public Base{
    int print(){
        cout<< " i am the child" <<endl;    
    }
    int print2(){
        cout<<" i am the child2" <<endl;
    }
};



int  main(){
    Base testBase;
    //using fun=int(*)();
    typedef int(*fun) (void);
    fun print=(fun)*((int *)*(int *)(&testBase));
    print();
    cout<<(int *)*(int *)(&testBase)<<endl;
    cout<<((int *)*(int *)(&testBase))+1<<endl;
    fun pprint2 = (fun)*((int *)*(int *)(&testBase)+1);
    pprint2();
    return 0;
} 

g++編譯后生成的內(nèi)存布局信息為:

Vtable for Base
Base::_ZTV4Base: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI4Base)
8     (int (*)(...))Base::print
12    (int (*)(...))Base::print2

Class Base
   size=8 align=4
   base size=8 base align=4
Base (0x0x4e057a8) 0
    vptr=((& Base::_ZTV4Base) + 8u)

Vtable for child
child::_ZTV5child: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5child)
8     (int (*)(...))child::print
12    (int (*)(...))child::print2

Class child
   size=8 align=4
   base size=8 base align=4
child (0x0x4e4b280) 0
    vptr=((& child::_ZTV5child) + 8u)
  Base (0x0x4e057e0) 0
      primary-for child (0x0x4e4b280)

Vtable表示的是虛函數(shù)表,可以發(fā)現(xiàn)Vtable for Base里面保存了vptr弯屈, 而且位置是vptr=((& Base::_ZTV4Base) + 8u)(注意這里加了8的偏移)蜗帜,這個位置的第一個內(nèi)容就是(int (*)(...))Base::print。

復雜的情況:多重繼承與部分虛函數(shù)覆蓋

#include <iostream>
using namespace std;
class Parent {
public:
    int iparent;
    Parent ():iparent (10) {}
    virtual void g() { cout << " Parent::g()" << endl; }
    virtual void f() { cout << " Parent::f()" << endl; }
    virtual void h() { cout << " Parent::h()" << endl; }
 
};
 
class Child : public Parent {
public:
    int ichild;
    Child():ichild(100) {}
    virtual void g_child() { cout << "Child::g_child()" << endl; }
    virtual void h_child() { cout << "Child::h_child()" << endl; }
    virtual void f() { cout << "Child::f()" << endl; }
};
 
class GrandChild : public Child{
public:
    int igrandchild;
    GrandChild():igrandchild(1000) {}
    virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
    virtual void f() { cout << "GrandChild::f()" << endl; }
    virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
int main(){
    typedef void(*Fun)(void);
    GrandChild gc;   
    int** pVtab = (int**)&gc;
     
    cout << "[0] GrandChild::_vptr->" << endl;
    for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){
        Fun pFun = (Fun)pVtab[0][i];
        cout << "    ["<<i<<"] ";
        pFun();
    }
    cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
    cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
    cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
} 

內(nèi)存布局信息如下

Vtable for Parent
Parent::_ZTV6Parent: 5u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI6Parent)
8     (int (*)(...))Parent::g
12    (int (*)(...))Parent::f
16    (int (*)(...))Parent::h

Class Parent
   size=8 align=4
   base size=8 base align=4
Parent (0x0x4de37a8) 0
    vptr=((& Parent::_ZTV6Parent) + 8u)

Vtable for Child
Child::_ZTV5Child: 7u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5Child)
8     (int (*)(...))Parent::g
12    (int (*)(...))Child::f
16    (int (*)(...))Parent::h
20    (int (*)(...))Child::g_child
24    (int (*)(...))Child::h_child

Class Child
   size=12 align=4
   base size=12 base align=4
Child (0x0x4e2a5c0) 0
    vptr=((& Child::_ZTV5Child) + 8u)
  Parent (0x0x4de37e0) 0
      primary-for Child (0x0x4e2a5c0)

Vtable for GrandChild
GrandChild::_ZTV10GrandChild: 8u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI10GrandChild)
8     (int (*)(...))Parent::g
12    (int (*)(...))GrandChild::f
16    (int (*)(...))Parent::h
20    (int (*)(...))GrandChild::g_child
24    (int (*)(...))Child::h_child
28    (int (*)(...))GrandChild::h_grandchild

Class GrandChild
   size=16 align=4
   base size=16 base align=4
GrandChild (0x0x4e2aa80) 0
    vptr=((& GrandChild::_ZTV10GrandChild) + 8u)
  Child (0x0x4e2aac0) 0
      primary-for GrandChild (0x0x4e2aa80)
    Parent (0x0x4de3818) 0
        primary-for Child (0x0x4e2aac0)

通過查看可以發(fā)現(xiàn)资厉,子類的虛函數(shù)表里面的優(yōu)先級是先父類厅缺,再子類再孫類,同一優(yōu)先級按照聲明順序排地址,即使父類的虛函數(shù)被覆蓋了湘捎,也要寫在原來的位置诀豁,這樣能夠保證,父類的指針能按照函數(shù)名找到那個地址窥妇。

程序的執(zhí)行結(jié)果為:

[0] GrandChild::_vptr->
<pre>    [0] GrandChild::f()
    [1] Parent::g()
    [2] Parent::h()
    [3] GrandChild::g_child()
    [4] Child::h1()
    [5] GrandChild::h_grandchild()
[1] Parent.iparent = 10
[2] Child.ichild = 100
[3] GrandChild.igrandchild = 1000

可知

  1. 虛函數(shù)表在最前面的位置舷胜。
  2. 成員變量根據(jù)其繼承和聲明順序依次放在后面。
  3. 在單一的繼承中活翩,被overwrite的虛函數(shù)在虛函數(shù)表中得到了更新烹骨。

多重繼承

#include <iostream>
using namespace std;
class Base1 {
public:
    int ibase1;
    Base1():ibase1(10) {}
    virtual void f() { cout << "Base1::f()" << endl; }
    virtual void g() { cout << "Base1::g()" << endl; }
    virtual void h() { cout << "Base1::h()" << endl; }
 
};
 
class Base2 {
public:
    int ibase2;
    Base2():ibase2(20) {}
    virtual void f() { cout << "Base2::f()" << endl; }
    virtual void g() { cout << "Base2::g()" << endl; }
    virtual void h() { cout << "Base2::h()" << endl; }
};
 
class Base3 {
public:
    int ibase3;
    Base3():ibase3(30) {}
    virtual void f() { cout << "Base3::f()" << endl; }
    virtual void g() { cout << "Base3::g()" << endl; }
    virtual void h() { cout << "Base3::h()" << endl; }
};
 
class Derive : public Base1, public Base2, public Base3 {
public:
    int iderive;
    Derive():iderive(100) {}
    virtual void f() { cout << "Derive::f()" << endl; }
    virtual void g1() { cout << "Derive::g1()" << endl; }
};
int main(){
    typedef void(*Fun)(void);
     
    Derive d;
     
    int** pVtab = (int**)&d;
     
    cout << "[0] Base1::_vptr->" << endl;
    Fun pFun = (Fun)pVtab[0][0];
    cout << "     [0] ";
    pFun();
     
    pFun = (Fun)pVtab[0][1];
    cout << "     [1] ";pFun();
     
    pFun = (Fun)pVtab[0][2];
    cout << "     [2] ";pFun();
     
    pFun = (Fun)pVtab[0][3];
    cout << "     [3] "; pFun();
     
    pFun = (Fun)pVtab[0][4];
    cout << "     [4] "; cout<<pFun<<endl;
     
    cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
     
    int s = sizeof(Base1)/4;
     
    cout << "[" << s << "] Base2::_vptr->"<<endl;
    pFun = (Fun)pVtab[s][0];
    cout << "     [0] "; pFun();
     
    pFun = (Fun)pVtab[s][1];
    cout << "     [1] "; pFun();
     
    pFun = (Fun)pVtab[s][2];
    cout << "     [2] "; pFun();
     
    pFun = (Fun)pVtab[s][3];
    cout << "     [3] ";
    cout<<pFun<<endl;
     
    cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;
     
    s = s + sizeof(Base2)/4;
     
    cout << "[" << s << "] Base3::_vptr->"<<endl;
    pFun = (Fun)pVtab[s][0];
    cout << "     [0] "; pFun();
     
    pFun = (Fun)pVtab[s][1];
    cout << "     [1] "; pFun();
     
    pFun = (Fun)pVtab[s][2];
    cout << "     [2] "; pFun();
     
    pFun = (Fun)pVtab[s][3];
    cout << "     [3] ";
    cout<<pFun<<endl;
     
    s++;
    cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;
    s++;
    cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;
    return 0; 
} 

內(nèi)存分布情況如下

Vtable for Base1
Base1::_ZTV5Base1: 5u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5Base1)
8     (int (*)(...))Base1::f
12    (int (*)(...))Base1::g
16    (int (*)(...))Base1::h

Class Base1
   size=8 align=4
   base size=8 base align=4
Base1 (0x0x4d907a8) 0
    vptr=((& Base1::_ZTV5Base1) + 8u)

Vtable for Base2
Base2::_ZTV5Base2: 5u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5Base2)
8     (int (*)(...))Base2::f
12    (int (*)(...))Base2::g
16    (int (*)(...))Base2::h

Class Base2
   size=8 align=4
   base size=8 base align=4
Base2 (0x0x4d907e0) 0
    vptr=((& Base2::_ZTV5Base2) + 8u)

Vtable for Base3
Base3::_ZTV5Base3: 5u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5Base3)
8     (int (*)(...))Base3::f
12    (int (*)(...))Base3::g
16    (int (*)(...))Base3::h

Class Base3
   size=8 align=4
   base size=8 base align=4
Base3 (0x0x4d90818) 0
    vptr=((& Base3::_ZTV5Base3) + 8u)

Vtable for Derive
Derive::_ZTV6Derive: 16u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI6Derive)
8     (int (*)(...))Derive::f
12    (int (*)(...))Base1::g
16    (int (*)(...))Base1::h
20    (int (*)(...))Derive::g1
24    (int (*)(...))-8
28    (int (*)(...))(& _ZTI6Derive)
32    (int (*)(...))Derive::_ZThn8_N6Derive1fEv
36    (int (*)(...))Base2::g
40    (int (*)(...))Base2::h
44    (int (*)(...))-16
48    (int (*)(...))(& _ZTI6Derive)
52    (int (*)(...))Derive::_ZThn16_N6Derive1fEv
56    (int (*)(...))Base3::g
60    (int (*)(...))Base3::h

Class Derive
   size=28 align=4
   base size=28 base align=4
Derive (0x0x4ddeb40) 0
    vptr=((& Derive::_ZTV6Derive) + 8u)
  Base1 (0x0x4d90850) 0
      primary-for Derive (0x0x4ddeb40)
  Base2 (0x0x4d90888) 8
      vptr=((& Derive::_ZTV6Derive) + 32u)
  Base3 (0x0x4d908c0) 16
      vptr=((& Derive::_ZTV6Derive) + 52u)

程序的輸出結(jié)果為

[0] Base1::_vptr->
     [0] Derive::f()
     [1] Base1::g()
     [2] Base1::h()
     [3] Derive::g1()
     [4] 1
[1] Base1.ibase1 = 10
[2] Base2::_vptr->
     [0] Derive::f()
     [1] Base2::g()
     [2] Base2::h()
     [3] 1
[3] Base2.ibase2 = 20
[4] Base3::_vptr->
     [0] Derive::f()
     [1] Base3::g()
     [2] Base3::h()
     [3] 0
[5] Base3.ibase3 = 30
[6] Derive.iderive = 100

結(jié)論:

  1. 每個父類都有自己的虛表。
  2. 子類的成員函數(shù)被放到了第一個父類的表中材泄。
  3. 內(nèi)存布局中沮焕,其父類布局依次按聲明順序排列。
  4. 每個父類的虛表中的f()函數(shù)都被overwrite成了子類的f()拉宗。這樣做就是為了解決不同的父類類型的指針指向同一個子類實例峦树,而能夠調(diào)用到實際的函數(shù)。

多重繼承

#include <iostream>
using namespace std;
class B
{
    public:
        int ib;
        char cb;
    public:
        B():ib(0),cb('B') {}
 
        virtual void f() { cout << "B::f()" << endl;}
        virtual void Bf() { cout << "B::Bf()" << endl;}
};
class B1 :  public B
{
    public:
        int ib1;
        char cb1;
    public:
        B1():ib1(11),cb1('1') {}
 
        virtual void f() { cout << "B1::f()" << endl;}
        virtual void f1() { cout << "B1::f1()" << endl;}
        virtual void Bf1() { cout << "B1::Bf1()" << endl;}
 
};
class B2:  public B
{
    public:
        int ib2;
        char cb2;
    public:
        B2():ib2(12),cb2('2') {}
 
        virtual void f() { cout << "B2::f()" << endl;}
        virtual void f2() { cout << "B2::f2()" << endl;}
        virtual void Bf2() { cout << "B2::Bf2()" << endl;}
 
};
 
class D : public B1, public B2
{
    public:
        int id;
        char cd;
    public:
        D():id(100),cd('D') {}
 
        virtual void f() { cout << "D::f()" << endl;}
        virtual void f1() { cout << "D::f1()" << endl;}
        virtual void f2() { cout << "D::f2()" << endl;}
        virtual void Df() { cout << "D::Df()" << endl;}
 
};
int main(){
    typedef void(*Fun)(void);
    int** pVtab = NULL;
    Fun pFun = NULL;
     
    D d;
    pVtab = (int**)&d;
    cout << "[0] D::B1::_vptr->" << endl;
    pFun = (Fun)pVtab[0][0];
    cout << "     [0] ";    pFun();
    pFun = (Fun)pVtab[0][1];
    cout << "     [1] ";    pFun();
    pFun = (Fun)pVtab[0][2];
    cout << "     [2] ";    pFun();
    pFun = (Fun)pVtab[0][3];
    cout << "     [3] ";    pFun();
    pFun = (Fun)pVtab[0][4];
    cout << "     [4] ";    pFun();
    pFun = (Fun)pVtab[0][5];
    cout << "     [5] 0x" << pFun << endl;
     
    cout << "[1] B::ib = " << (int)pVtab[1] << endl;
    cout << "[2] B::cb = " << static_cast<char>((int)(pVtab[2]))<< endl;
    cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
    cout << "[4] B1::cb1 = " << (char)(int)pVtab[4] << endl;
     
    cout << "[5] D::B2::_vptr->" << endl;
    pFun = (Fun)pVtab[5][0];
    cout << "     [0] ";    pFun();
    pFun = (Fun)pVtab[5][1];
    cout << "     [1] ";    pFun();
    pFun = (Fun)pVtab[5][2];
    cout << "     [2] ";    pFun();
    pFun = (Fun)pVtab[5][3];
    cout << "     [3] ";    pFun();
    pFun = (Fun)pVtab[5][4];
    cout << "     [4] 0x" << pFun << endl;
     
    cout << "[6] B::ib = " << (int)pVtab[6] << endl;
    cout << "[7] B::cb = " << (char)(int)pVtab[7] << endl;
    cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
    cout << "[9] B2::cb2 = " << (char)(int)pVtab[9] << endl;
     
    cout << "[10] D::id = " << (int)pVtab[10] << endl;
    cout << "[11] D::cd = " << (char)(int)pVtab[11] << endl;
    return 0; 
} 

內(nèi)存分布情況如下


Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1B)
8     (int (*)(...))B::f
12    (int (*)(...))B::Bf

Class B
   size=12 align=4
   base size=9 base align=4
B (0x0x4dc27a8) 0
    vptr=((& B::_ZTV1B) + 8u)

Vtable for B1
B1::_ZTV2B1: 6u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI2B1)
8     (int (*)(...))B1::f
12    (int (*)(...))B::Bf
16    (int (*)(...))B1::f1
20    (int (*)(...))B1::Bf1

Class B1
   size=20 align=4
   base size=17 base align=4
B1 (0x0x4e09780) 0
    vptr=((& B1::_ZTV2B1) + 8u)
  B (0x0x4dc27e0) 0
      primary-for B1 (0x0x4e09780)

Vtable for B2
B2::_ZTV2B2: 6u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI2B2)
8     (int (*)(...))B2::f
12    (int (*)(...))B::Bf
16    (int (*)(...))B2::f2
20    (int (*)(...))B2::Bf2

Class B2
   size=20 align=4
   base size=17 base align=4
B2 (0x0x4e09c40) 0
    vptr=((& B2::_ZTV2B2) + 8u)
  B (0x0x4dc2818) 0
      primary-for B2 (0x0x4e09c40)

Vtable for D
D::_ZTV1D: 14u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1D)
8     (int (*)(...))D::f
12    (int (*)(...))B::Bf
16    (int (*)(...))D::f1
20    (int (*)(...))B1::Bf1
24    (int (*)(...))D::f2
28    (int (*)(...))D::Df
32    (int (*)(...))-20
36    (int (*)(...))(& _ZTI1D)
40    (int (*)(...))D::_ZThn20_N1D1fEv
44    (int (*)(...))B::Bf
48    (int (*)(...))D::_ZThn20_N1D2f2Ev
52    (int (*)(...))B2::Bf2

Class D
   size=48 align=4
   base size=45 base align=4
D (0x0x4e27040) 0
    vptr=((& D::_ZTV1D) + 8u)
  B1 (0x0x4e27080) 0
      primary-for D (0x0x4e27040)
    B (0x0x4dc2850) 0
        primary-for B1 (0x0x4e27080)
  B2 (0x0x4e270c0) 20
      vptr=((& D::_ZTV1D) + 40u)
    B (0x0x4dc2888) 20
        primary-for B2 (0x0x4e270c0)

輸出結(jié)果為

[0] D::B1::_vptr->
     [0] D::f()
     [1] B::Bf()
     [2] D::f1()
     [3] B1::Bf1()
     [4] D::f2()
     [5] 0x1
[1] B::ib = 0
[2] B::cb = B
[3] B1::ib1 = 11
[4] B1::cb1 = 1
[5] D::B2::_vptr->
     [0] D::f()
     [1] B::Bf()
     [2] D::f2()
     [3] B2::Bf2()
     [4] 0x0
[6] B::ib = 0
[7] B::cb = B
[8] B2::ib2 = 12
[9] B2::cb2 = 2
[10] D::id = 100
[11] D::cd = D

運行結(jié)果為:


發(fā)現(xiàn)最頂端的父類B其成員變量和虛函數(shù)存在于B1和B2中簿废,并被D給繼承下去了空入。而在D中,其有B1和B2的實例族檬,于是B的成員在D的實例中存在兩份歪赢,一份是B1繼承而來的,另一份是B2繼承而來的单料,因此就會浪費內(nèi)存埋凯。

盡管我們可以通過明確指明調(diào)用路徑以消除二義性,但二義性的潛在性還沒有消除扫尖,我們可以通過虛繼承來使D類只擁有一個ib實體白对。

虛繼承

虛繼承解決了菱形繼承中最派生類擁有多個間接父類實例的情況。虛繼承的派生類的內(nèi)存布局與普通繼承很多不同换怖,主要體現(xiàn)在:

  1. 虛繼承的子類甩恼,如果本身定義了新的虛函數(shù),則編譯器為其生成一個虛函數(shù)指針(vptr)以及一張?zhí)摵瘮?shù)表沉颂。該vptr位于對象內(nèi)存最前面条摸。vs非虛繼承:直接擴展父類虛函數(shù)表。
  2. 虛繼承的子類也單獨保留了父類的vprt與虛函數(shù)表铸屉。這部分內(nèi)容接與子類內(nèi)容以一個四字節(jié)的0來分界钉蒲。
  3. 虛繼承的子類對象中,含有四字節(jié)的虛表指針偏移值彻坛。

簡單虛繼承

#include <iostream>
using namespace std;
class B
{
public:
    int ib;
public:
    B(int i=1) :ib(i){}
    virtual void f() { cout << "B::f()" << endl; }
    virtual void Bf() { cout << "B::Bf()" << endl; }
};
 
class B1 : virtual public B
{
public:
    int ib1;
public:
    B1(int i = 100 ) :ib1(i) {}
    virtual void f() { cout << "B1::f()" << endl; }
    virtual void f1() { cout << "B1::f1()" << endl; }
    virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};
int main(){
    B1 b;
    using Fun=void(*)();
    Fun pFun = NULL;
    int ** pvtable = (int**)&b;
    for (int i=0; i<9; i++){
        if (pvtable[0][i]==NULL) {
            cout<<"here:"<<(int)pvtable[0][i]<<endl;
            i+=4;
            continue;
        }
        pFun = (Fun)pvtable[0][i];
        pFun();
    }
    cout<<"size == "<<sizeof(b)<<endl;
    cout<<"[1]:B1::ib1="<<(int)pvtable[1]<<endl;
    cout<<"[3]:B::ib="<<(int)pvtable[3]<<endl;
    cout<<"[2]:B::ib="<<(int)pvtable[2]<<endl;
//  pFun = (Fun)pvtable[2];
//  pFun();
//  for (int i=1; pvtable[1][i]!=NULL; i++){
//      pFun = (Fun)pvtable[1][i];
//      pFun();
//  }
    
    return 0; 
} 


內(nèi)存布局

Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1B)
8     (int (*)(...))B::f
12    (int (*)(...))B::Bf

Class B
   size=8 align=4
   base size=8 base align=4
B (0x0x4f527a8) 0
    vptr=((& B::_ZTV1B) + 8u)

Vtable for B1
B1::_ZTV2B1: 12u entries
0     8u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI2B1)
12    (int (*)(...))B1::f
16    (int (*)(...))B1::f1
20    (int (*)(...))B1::Bf1
24    0u
28    4294967288u
32    (int (*)(...))-8
36    (int (*)(...))(& _ZTI2B1)
40    (int (*)(...))B1::_ZTv0_n12_N2B11fEv
44    (int (*)(...))B::Bf

VTT for B1
B1::_ZTT2B1: 2u entries
0     ((& B1::_ZTV2B1) + 12u)
4     ((& B1::_ZTV2B1) + 40u)

Class B1
   size=16 align=4
   base size=8 base align=4
B1 (0x0x4f9a540) 0
    vptridx=0u vptr=((& B1::_ZTV2B1) + 12u)
  B (0x0x4f527e0) 8 virtual
      vptridx=4u vbaseoffset=-12 vptr=((& B1::_ZTV2B1) + 40u)

程序運行結(jié)果


B1::f()
B1::f1()
B1::Bf1()
here:0
B::Bf()
size == 16
[1]:B1::ib1=100
[3]:B::ib=1
[2]:B::ib=4781096

這里的內(nèi)存分布比較奇怪顷啼,與vc++的結(jié)果不一樣踏枣。

但是這里size是16 說明有兩個指針;

多重虛繼承

菱形虛擬繼承下钙蒙,最派生類D類的對象模型又有不同的構(gòu)成了茵瀑。在D類對象的內(nèi)存構(gòu)成上,有以下幾點:

  1. 在D類對象內(nèi)存中仪搔,基類出現(xiàn)的順序是:先是B1(最左父類)瘾婿,然后是B2(次左父類)蜻牢,最后是B(虛祖父類)
  2. D類對象的數(shù)據(jù)成員id放在B類前面烤咧,兩部分數(shù)據(jù)依舊以0來分隔。
  3. 編譯器沒有為D類生成一個它自己的vptr抢呆,而是覆蓋并擴展了最左父類的虛基類表煮嫌,與簡單繼承的對象模型相同。
  4. 超類B的內(nèi)容放到了D類對象內(nèi)存布局的最后抱虐。

參考資料

C/C++內(nèi)存對齊詳解

C++ 對象的內(nèi)存布局——coolshell

圖說C++對象模型:對象內(nèi)存布局詳解

各編譯器顯示內(nèi)存布局

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昌阿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恳邀,更是在濱河造成了極大的恐慌懦冰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谣沸,死亡現(xiàn)場離奇詭異刷钢,居然都是意外死亡,警方通過查閱死者的電腦和手機乳附,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門内地,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赋除,你說我怎么就攤上這事阱缓。” “怎么了举农?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵荆针,是天一觀的道長。 經(jīng)常有香客問我颁糟,道長航背,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任滚停,我火速辦了婚禮沃粗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘键畴。我一直安慰自己最盅,他們只是感情好突雪,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涡贱,像睡著了一般咏删。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上问词,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天督函,我揣著相機與錄音,去河邊找鬼激挪。 笑死辰狡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的垄分。 我是一名探鬼主播宛篇,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼薄湿!你這毒婦竟也來了叫倍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤豺瘤,失蹤者是張志新(化名)和其女友劉穎吆倦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坐求,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蚕泽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞻赶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赛糟。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖砸逊,靈堂內(nèi)的尸體忽然破棺而出璧南,到底是詐尸還是另有隱情,我是刑警寧澤师逸,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布司倚,位于F島的核電站,受9級特大地震影響篓像,放射性物質(zhì)發(fā)生泄漏动知。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一员辩、第九天 我趴在偏房一處隱蔽的房頂上張望盒粮。 院中可真熱鬧,春花似錦奠滑、人聲如沸丹皱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摊崭。三九已至讼油,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呢簸,已是汗流浹背矮台。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留根时,地道東北人瘦赫。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像啸箫,于是被迫代替她去往敵國和親耸彪。 傳聞我的和親對象是個殘疾皇子伞芹,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,089評論 1 32
  • 一個博客忘苛,這個博客記錄了他讀這本書的筆記,總結(jié)得不錯唱较≡伲《深度探索C++對象模型》筆記匯總 1. C++對象模型與內(nèi)...
    Mr希靈閱讀 5,566評論 0 13
  • 前言 這本書是之前京東做活動買的很多本書中的一本(主要閱讀時間是周末、每天早上起來吃早餐的時候南缓,以及下班回來時候胸遇,...
    ampire_dan閱讀 1,220評論 0 1
  • 幾種語言的特性 匯編程序:將匯編語言源程序翻譯成目標程序編譯程序:將高級語言源程序翻譯成目標程序解釋程序:將高級語...
    囊螢映雪的螢閱讀 2,867評論 1 5
  • struct與class的區(qū)別 C的struct與C++的class的區(qū)別:struct只是作為一種復雜數(shù)據(jù)類型定...
    geekzph閱讀 1,564評論 0 4