从编译器的辅助信息看c++对象内存布局

  • 编程
  • cpp

预知识

本文的内容使用的是32位的编译器编译出的结果,可以打印出类的内存布局信息

DevCPP IDE

这个IDE是我比较喜欢的windows下的cpp的IDE之一,它有一个工具->编译选项,可以选择编译器类型,也可以在编译选项中加入一些信息,为了能够输出内存布局信息,我在编译时加入以下命令

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

-fdump-class-hierarchy 这个选项能够在gcc编译时生成类的布局信息,生成的文件名类似为6.cpp.002t.class

vs2017

vs使用的编译器是cl,它的命令为
/d1reportAllClassLayout: 输出所有类相关布局

clang

clang -Xclang -fdump-record-layouts

理解内存布局信息

测试代码如下

class father{
    int a;
    int b;
};

class child: public father{
};

int main(){
    return 0;
}

使用 g++ -fdump-class-hierarchy test.cpp 生成了 test.cpp.002t.class内容如下

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

显示了类的大小和对齐信息。

题话外: 理解内存对齐

  1. 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
  2. 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

多态与虚表

多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态——当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数。

虚函数指针一般都放在对象内存布局的第一个位置上,这是为了保证在多层继承或多重继承的情况下能以最高效率取到虚函数表。当vprt位于对象内存最前面时,对象的地址即为虚函数指针地址。我们可以取得虚函数指针的地址

下面的方式取得虚函数指针的地址(而非虚函数指针指向的地址)

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

vptrAdree指向了虚函数指针(指向虚函数表),

而我们可以通过如下的方式获得第一个虚函数的地址

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

简单的情况,单层继承与虚函数全覆盖

通过查看编译器生成的内存布局信息来具体地看:

#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++编译后生成的内存布局信息为:

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表示的是虚函数表,可以发现Vtable for Base里面保存了vptr, 而且位置是vptr=((& Base::_ZTV4Base) + 8u)(注意这里加了8的偏移),这个位置的第一个内容就是(int (*)(...))Base::print。

复杂的情况:多重继承与部分虚函数覆盖

#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;
} 

内存布局信息如下

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)

通过查看可以发现,子类的虚函数表里面的优先级是先父类,再子类再孙类,同一优先级按照声明顺序排地址,即使父类的虚函数被覆盖了,也要写在原来的位置,这样能够保证,父类的指针能按照函数名找到那个地址。

程序的执行结果为:

[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. 虚函数表在最前面的位置。
  2. 成员变量根据其继承和声明顺序依次放在后面。
  3. 在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

多重继承

#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; 
} 

内存分布情况如下

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)

程序的输出结果为

[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

结论:

  1. 每个父类都有自己的虚表。
  2. 子类的成员函数被放到了第一个父类的表中。
  3. 内存布局中,其父类布局依次按声明顺序排列。
  4. 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承

#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; 
} 

内存分布情况如下


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)

输出结果为

[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

运行结果为:


发现最顶端的父类B其成员变量和虚函数存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的,因此就会浪费内存。

尽管我们可以通过明确指明调用路径以消除二义性,但二义性的潜在性还没有消除,我们可以通过虚继承来使D类只拥有一个ib实体。

虚继承

虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。虚继承的派生类的内存布局与普通继承很多不同,主要体现在:

  1. 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。vs非虚继承:直接扩展父类虚函数表。
  2. 虚继承的子类也单独保留了父类的vprt与虚函数表。这部分内容接与子类内容以一个四字节的0来分界。
  3. 虚继承的子类对象中,含有四字节的虚表指针偏移值。

简单虚继承

#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; 
} 


内存布局

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)

程序运行结果


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

这里的内存分布比较奇怪,与vc++的结果不一样。

但是这里size是16 说明有两个指针;

多重虚继承

菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:

  1. 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
  2. D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
  3. 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
  4. 超类B的内容放到了D类对象内存布局的最后。

参考资料

C/C++内存对齐详解

C++ 对象的内存布局——coolshell

图说C++对象模型:对象内存布局详解

各编译器显示内存布局

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 一个博客,这个博客记录了他读这本书的笔记,总结得不错。《深度探索C++对象模型》笔记汇总 1. C++对象模型与内...
    Mr希灵阅读 5,566评论 0 13
  • 前言 这本书是之前京东做活动买的很多本书中的一本(主要阅读时间是周末、每天早上起来吃早餐的时候,以及下班回来时候,...
    ampire_dan阅读 1,217评论 0 1
  • 几种语言的特性 汇编程序:将汇编语言源程序翻译成目标程序编译程序:将高级语言源程序翻译成目标程序解释程序:将高级语...
    囊萤映雪的萤阅读 2,867评论 1 5
  • struct与class的区别 C的struct与C++的class的区别:struct只是作为一种复杂数据类型定...
    geekzph阅读 1,564评论 0 4