转载:虚函数和虚继承的内存分布

来源:互联网 发布:c语言经典实例 编辑:程序博客网 时间:2024/05/18 08:26

最近做题的时候经常遇到虚函数和虚继承的内存分布情况问题,自己也有点生疏了,所以,赶紧在这里回忆补充一下!


先从一个类的内存分布情况开始看起:

环境:VS2012

class A{    int a;public:    A()        :a(1)    {}    void fun()    {        cout<<"A"<<endl;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

按照我的理解,内存中只有一个int a; 这个类的大小为 4;下面写个测试:

    A a;    cout<<sizof(a)<<endl;
  • 1
  • 2
  • 1
  • 2

先通过调试看内存情况:

这里写图片描述

sizeof(a) 的结果:

这里写图片描述

结果和预想的一样,当然这只是复习的一个热身;

将fun 变为虚函数:

    virtual void fun()    {        cout<<"A"<<endl;    }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

先通过调试看内存情况:

这里写图片描述

查看 sizeof(a)的大小:

这里写图片描述

我们可以看到,在类中加入虚函数之后,实例之后的对象会有一个虚函数指针,指向一个虚函数表,而虚函数表中存放的是虚函数地址!


下面我们来一个B类继承A类,看看内存的分布:

class B: public A{    int b;public:    B()        :b(2)    {}    void fun()    {        cout<<"B"<<endl;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

B继承A,并且有一个成员 b,重写了A的fun;

测试:

    B b;    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
  • 1
  • 2
  • 1
  • 2

调试查看b的内存布局:

这里写图片描述

内存布局分析: 我们可以看到先存放的是A 的成员 a, 然后存放的才是 B的成员 b;

sizeof(b) 的结果:

这里写图片描述

此时我们再让A的fun为虚函数,看看 B继承 A 之后b的内存分布:

这里写图片描述

sizeof(b) 的 大小:

这里写图片描述

分析: 此时b的内存布局是:先是一个虚表指针,然后是 a , b;


我们再看看一个类分别继承 A 和 B的情况:此时是这样的情况

这里写图片描述

class C: public A, public B{    int c;public:    C()        :c(3)    {}    void fun()    {        cout<<"C"<<endl;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

C类先继承 A 再继承 B 类, 注意:此时我们B类没有继承 A 类,并且没有虚函数

测试

 C c; cout<<"sizeof(c) = "<<sizeof(c)<<endl;
  • 1
  • 2
  • 1
  • 2

调试查看内存:

这里写图片描述

查看 sizeof(c) 的结果:

这里写图片描述

此时将 A 和 B 的fun设为虚函数,看看 c 的内存布局:

这里写图片描述

sizeof(c)的结果

这里写图片描述

分析: 内存布局,首先是一个虚表指针,和A的一致,然后是a的成员,接着是B的虚表指针,B的数据成员, 最后是C的数据成员;


感觉情况好多,后面的sizeof的结果就不截图了;

到这里,我们可以对含有虚函数的类的对象的内存布局做一个总结:

首先,内存的开始一定是一个虚表指针,指向一个虚表,如果含有继承,则和第一个继承的基类的虚表指针一致,然后先存放基类的数据成员,再存放自己的数据成员;还有一点,那就是如果子类中对基类的虚函数进行了重写,那么会在虚表中覆盖掉基类虚函数的地址!


下面我们要看的是多重继承的内存分布情况,这里就只看一种:菱形继承;

什么是菱形继承?

上面我们举过一个例子, c 分别继承 A 和 B; 如果我让 A 和 B 分别再继承一个基类 Base 的话,这就叫菱形继承:

大概逻辑关系如下: 没有虚函数

这里写图片描述

class Base{    int base;public:    Base()        :base(0)    {}};class A:public Base{    int a;public:    A()        :a(1)    {}    void fun()    {        cout<<"A"<<endl;    }};class B : public Base{    int b;public:    B()        :b(2)    {}     void fun()    {        cout<<"B"<<endl;    }};class C: public A, public B{    int c;public:    C()        :c(3)    {}    void fun()    {        cout<<"C"<<endl;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

那么此时如果有一个 C 的对象 c 的话,c的内存布局应该是怎么样的呢?

我们看看 c 的内存布局:

这里写图片描述

意料之中的布局,但是有一个问题,c 从A继承了一个 Base 的 base,又从B继承了一个Base 的 base,那么我们在调用的时候到底用的是哪一个? 而且如果base是一个非常大的结构体的话,每一个继承都需要创建一个base,那么岂不是很浪费空间,这就是多重继承的二义性和冗余的问题;

而虚继承存在的目的便是为了解决这个问题,那么它是如何解决的呢?

即使得A和B都虚继承Base,那么Base就是一个虚基类,只存储一份,而A和B中关于base的存储,都是存储一个指针,指向一个base, 因为把Base的成员存放在了内存的最后,这样根据相对偏移量就找到了这个共有的base;

说了这些都是纸上谈兵,终觉浅,我们还是直接来查看内存的布局吧;

class A:virtual public Base{    int a;public:    A()        :a(1)    {}    void fun()    {        cout<<"A"<<endl;    }};class B : virtual public Base{    int b;public:    B()        :b(2)    {}     void fun()    {        cout<<"B"<<endl;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

实例一个C 的对象 c ,看内存布局:

这里写图片描述

我们可以 看到原本应该是00 00 00 00的位置被指针代替了,而base只在最后存了一份;

实际上这两个指针指向一个虚基表,记录当前位置到最底下base的偏移量;这样就达到了共享的目的;

如果对于虚基类只被构造了一次抱有怀疑,可以在前面的测试中的构造函数中加入测试语句查看;

先复习到这吧,,,好累,后面继续补充!

这里写图片描述

阅读全文
0 0
原创粉丝点击