• [C++] 虚继承对C++对象内存模型的影响


    测试环境

         平台:32位

         编译环境:VS2008

    虚继承相关背景

         如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的属性和方法(假设这些属性和方法是公有的且都是公有继承)。从设计角度讲,这个实现是错误的,它容易产生二义性且浪费内存空间。

         虚继承可以解决这个问题,虚继承可以为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。

    问题:从一个例子开始

         假设有虚继承结构如下,虚基类A中有int成员a_;B1虚继承自A,有int成员b1_;B2虚继承自A,有int成员b2_;D继承自B1、B2,有int成员d_。

         在32位架构下,类A、B1、B2、D的大小?哎!我比较笨!只好写个程序看看结果先。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6 public :
     7     int a_ ;
     8 };
     9 
    10 class B1 : virtual public A
    11 {
    12 public :
    13     int b1_ ;
    14 };
    15 
    16 class B2 : virtual public A
    17 {
    18 public :
    19     int b2_ ;
    20 };
    21 
    22 class D : public B1, public B2
    23 {
    24 public :
    25     int d_ ;
    26 };
    27 
    28 int main (void)
    29 {
    30     cout << "sizeof(A) = " << sizeof(A) << endl;
    31     cout << "sizeof(B1) = " << sizeof(B1)<< endl;
    32     cout << "sizeof(D) = " << sizeof(D) << endl;
    33 
    34     return 0;
    35 }

        运行结果如下:

         sizeof(A)的结果符合我们的预期,但是sizeof(B1)和sizeof(D)的结果就有点不能理解了。

    分析:从查看B1类对象成员属性的地址开始

         我们不妨先构造一个B1类对象,然后打印该对象中的各个属性地址来看看究竟。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6 public :
     7     int a_ ;
     8 };
     9 
    10 class B1 : virtual public A
    11 {
    12 public :
    13     int b1_ ;
    14 };
    15 
    16 class B2 : virtual public A
    17 {
    18 public :
    19     int b2_ ;
    20 };
    21 
    22 class D : public B1, public B2
    23 {
    24 public :
    25     int d_ ;
    26 };
    27 
    28 int main (void)
    29 {
    30     cout << "sizeof(A) = " << sizeof(A) << endl;
    31     cout << "sizeof(B1) = " << sizeof(B1)<< endl;
    32     cout << "sizeof(D) = " << sizeof(D) << endl << endl;
    33 
    34     B1 ob1;
    35     cout << "&ob1 = 0x" << &ob1 << endl;
    36     cout << "&ob1.a_ = 0x" << &ob1.a_ << endl;
    37     cout << "&ob1.b1_ = 0x" << &ob1.b1_ << endl;
    38 
    39     return 0;
    40 }

         运行结果如下:

         我们发现ob1的首地址既不等于成员a_的地址也等于成员b1_的地址。那从首地址开始的4个字节存放的是什么内容呢?

    分析:B1类对象内存模型

         下面来分析原理,根据刚才获取的B1类大小以及该类对象中各个属性的地址分布,绘制B1类内存模型图如下:

         从内存模型图中可以看出:该对象的内存分为B1部分和虚基类部分。从B1部分的首地址开始的4个字节存放的是一个vbptr(B1部分的虚基类表指针),该指针指向了一个虚基类表(virtual base table of B1),这个虚基类表中存放了:

    • B1部分首地址vbptr(B1)所在地址之差,即0x0013F8AC - 0x0013F8AC = 0。
    • 虚基类部分首地址vbptr(B1)所在地址之差,即0x0013F8B4 - 0x0013F8AC = 8。

         好了,我们来验证下该内存模型是否正确。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6 public :
     7     int a_ ;
     8 };
     9 
    10 class B1 : virtual public A
    11 {
    12 public :
    13     int b1_ ;
    14 };
    15 
    16 class B2 : virtual public A
    17 {
    18 public :
    19     int b2_ ;
    20 };
    21 
    22 class D : public B1, public B2
    23 {
    24 public :
    25     int d_ ;
    26 };
    27 
    28 int main (void)
    29 {
    30     cout << "sizeof(A) = " << sizeof(A) << endl;
    31     cout << "sizeof(B1) = " << sizeof(B1)<< endl;
    32     cout << "sizeof(D) = " << sizeof(D) << endl << endl;
    33 
    34     B1 ob1;
    35     cout << "&ob1 = 0x" << &ob1 << endl;
    36     cout << "&ob1.a_ = 0x" << &ob1.a_ << endl;
    37     cout << "&ob1.b1_ = 0x" << &ob1.b1_ << endl << endl;
    38 
    39     long ** pVbptr = (long **)&ob1;
    40     cout << "virtual base table of B1 : " << endl;
    41     cout << "  [0] : " << pVbptr[0][0] << endl;
    42     cout << "  [1] : " << pVbptr[0][1] << endl;
    43 
    44     return 0;
    45 }

         运行结果如下:

    分析:D类对象内存模型

         有了前面的分析,下面的分析就简单多了。我们还是构造一个D类对象,然后打印下该对象中各个属性的地址。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6 public :
     7     int a_ ;
     8 };
     9 
    10 class B1 : virtual public A
    11 {
    12 public :
    13     int b1_ ;
    14 };
    15 
    16 class B2 : virtual public A
    17 {
    18 public :
    19     int b2_ ;
    20 };
    21 
    22 class D : public B1, public B2
    23 {
    24 public :
    25     int d_ ;
    26 };
    27 
    28 int main (void)
    29 {
    30     cout << "sizeof(A) = " << sizeof(A) << endl;
    31     cout << "sizeof(B1) = " << sizeof(B1)<< endl;
    32     cout << "sizeof(D) = " << sizeof(D) << endl << endl;
    33 
    34     D od;
    35     cout << "&od = 0x" << &od << endl;
    36     cout << "&od.a_ = 0x" << &od.a_ << endl;
    37     cout << "&od.b1_ = 0x" << &od.b1_ << endl;
    38     cout << "&od.b2 = 0x" << &od.b2_ << endl;
    39     cout << "&od.d_ = 0x" << &od.d_ << endl;
    40 
    41     return 0;
    42 }

         运行结果如下:

         根据运行结果绘制D类对象内存模型图如下:

        至于原理:在前面也已经说明了,这里就不再赘述了。

    扩展:访问D类对象的成员变量是如何实现的?

         当有一个虚基类指针指向了派生类对象,那么这次指向只是简单的赋值吗?

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6 public :
     7     int a_ ;
     8 };
     9 
    10 class B1 : virtual public A
    11 {
    12 public :
    13     int b1_ ;
    14 };
    15 
    16 class B2 : virtual public A
    17 {
    18 public :
    19     int b2_ ;
    20 };
    21 
    22 class D : public B1, public B2
    23 {
    24 public :
    25     int d_ ;
    26 };
    27 
    28 int main (void)
    29 {   
    30     D od;
    31     A* pa;
    32     pa = &od;
    33 
    34     return 0;
    35 }

         我们在32行设置下断点,单步运行后发现:

         pa真正的值不等于&od,更有意思的是它们的偏移量为0x001DFEBC - 0x001DFEA8,也就是20(10进制)。

         因此可以确定的是:在pa=&od执行时,程序内部会先通过该对象中的vbptr,然后通过vbptr(虚基类表指针)找到vbtbl(虚基类表),最终在vbtbl中找到虚基类表中虚基类部分与vbptr的偏移值,然后将指针指向加上偏移值的地址。

    总结

        本文针对虚继承情况,分析了在32位架构+VS2008编译环境下类对象内存模型,虽然有与分析内容相符合的运行结果,但这并不代表所有编译条件下(如g++)构造的内存模型都如上述情况(不同编译器:虚基类部分在整个对象模型中的位置会有些差异)。不过话又说回来,它们生成对象模型的原理其实都是类似的。

    (完)

  • 相关阅读:
    vue2.0:(三)、项目开始,首页入门(main.js,App.vue,importfrom)
    vue2.0:(二)、mock数据
    sublime text less安装踩坑图文讲解(less无法生成css)
    vue2.0:(一)、vue的安装和项目搭建(以外卖app项目举例)
    移动端开发(二)(初级入门)
    移动端开发(一)(初级入门)
    git与GitHub(二)
    git与GitHub(一)
    项目心得1
    MIPS(极路由1s[mt7620a])平台OpenWrt路由器系统内的Go应用程序开发
  • 原文地址:https://www.cnblogs.com/helloamigo/p/3589411.html
Copyright © 2020-2023  润新知