好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

虚拟多重继承的C++对象内存模型

虚拟多重继承的C++对象内存模型

虚拟多重继承的C++对象内存模型

关于C++对象内存布局的资料和书籍也有很多,比如陈皓老师的博客:

1、C++对象的内存布局(上)

2、C++对象的内存布局(下)

白杨:

RTTI、虚函数和虚基类的实现方式、开销分析及使用指导

左手为你画猜:

C++类对象内存模型与成员函数调用分析(上、中、下)

关于讲解C++对象内存模型最好的书应该是侯捷老师翻译的《深度探索C++对象内存模型》。

这两天在看其他书籍时,对C++中虚拟继承的实现机制不太理解,于是又重新翻回 《深度探索C++对象内存模型》一书,并结合 C++对象的内存布局(下) 一文。在Visual Studio 2010下用“cl”编译器进行测试,查看虚拟多重继承下的C++对象内存模型。总结如下:

一、重复继承

所谓重复继承,即某个基类被间接地重复继承了多次。为方便对比说明,下面的代码采用了陈皓老师博客中C++类例子。

UML类图如下:

类继承的源代码如下,直接采用 C++对象的内存布局(下) 中的例子,相关解释已在原博客中详细说明,故在此不再赘述:

  1  #include <iostream>
  2   using   namespace   std;
   3  
  4   class   B
   5   {
   6   public  :
   7       int   ib;
   8       char   cb;
   9   public  :
  10      B():ib( 0 ),cb( '  B  '  )
  11       {}
  12       virtual   void   f()
  13       {
  14          cout<< "  B::f()  " << endl;
  15       }
  16       virtual   void   Bf()
  17       {
  18          cout<< "  B::Bf()  " << endl;
  19       }
  20   };
  21  
 22   class  B1: public   B
  23   {
  24   public  :
  25       int   ib1;
  26       char   cb1;
  27   public  :
  28      B1():ib1( 01 ),cb1( '  1  '  ){}
  29  
 30       virtual   void   f()
  31       {
  32          cout<< "  B1::f()  " << endl;
  33       }
  34       virtual   void   f1()
  35       {
  36          cout<< "  B1::f1()  " << endl;
  37       }
  38       virtual   void   Bf1()
  39       {
  40          cout<< "  B1::Bf1()  " << endl;
  41       }
  42   };
  43  
 44   class  B2: public   B
  45   {
  46   public  :
  47       int   ib2;
  48       char   cb2;
  49   public  :
  50      B2():ib2( 10 ),cb2( '  2  '  ){}
  51       virtual   void   f()
  52       {
  53          cout<< "  B2::f()  " << endl;
  54       }
  55       virtual   void   f2()
  56       {
  57          cout<< "  B2::f2()  " << endl;
  58       }
  59       virtual   void   Bf2()
  60       {
  61          cout<< "  B2::Bf2()  " << endl;
  62       }
  63   };
  64  
 65   class  D:  public  B1,  public   B2
  66   {
  67   public  :
  68       int   id;
  69       char   cd;
  70   public  :
  71      D():id( 100 ),cd( '  D  '  ){}
  72  
 73       virtual   void   f()
  74       {
  75          cout<< "  D::f()  " << endl;
  76       }
  77       virtual   void   f1()
  78       {
  79          cout<< "  D::f1()  " << endl;
  80       }
  81       virtual   void   f2()
  82       {
  83          cout<< "  D::f2()  " << endl;
  84       }
  85       virtual   void   Df()
  86       {
  87          cout<< "  D::Df()  " << endl;
  88       }
  89  
 90   };
  91   int  main( int  argc,  char  * argv[])
  92   {
  93       D d;
  94      system( "  pause  "  );
  95       return   0  ;
  96  }

在陈皓老师博客中,直接利用函数指针调用C++对象起始位置处虚函数表指针指向的虚函数表中的虚函数,以查看C++对象的内存模型。下面我们主要采用Visual Studio 2010 和 Visual C++下的“cl”编译器查看C++对象内存模型。

在 Visual Studio 2010 IDE开发环境中,我们查看派生类D对象的内存模型。如下图所示:

   

从上两图我们可以基本看出:

1、 派生类D对象d的内存布局中,由其基类依次组装而成,再加上派生类自己的成员变量。

2、其中基类布局依次按照在派生类中的声明顺序排列。

3、每个基类都有自己的虚函数表,指向虚函数表的指针 _vfptr 放置在最前面的位置。

为了再进一步了解重复继承中的C++对象内存模型,我们采用Visual C++下的“cl”编译器进行查看。

在“Microsoft Visual C++”的编译环境中,我们可以利用编译器“cl”、链接器“link”、可执行文件查看器“dumpbin”来查看Windows下可执行文件(COFF格式)的变量、函数怎么存储。

“cl”即Visual C++ 的编译器,即“Compiler”的缩写。在Visual Studio 2010安装完后,会有一个批处理文件用来建立运行这些工具所需要的环境。它位于 开始/程序/Microsoft Visual Studio 2010/Visual Studio Tools/Viusual Studio 2010 Command Prompt ,这样我们就可以利用命令行使用VC++的编译器了。

在“cl”编译器中有个编译选项可以查看C++类的内存布局,使用如下:打开Visual Studio的命令行提示符即 Viusual Studio 2010 Command Prompt ,按如下格式输入:

>cl [.cpp] /d1reportSingleClassLayout[classname]

d1reportSingleClassLayout可以查看源文件中所有类及结构体的内存布局,classname为类名, /d1reportSingleClassLayout[classname]之间没有空格。使用如下图所示:

使用cl编译器查看重复继承中的C++对象内存模型结果如下图所示:

 从上图可以看出,编译器在实现时使用了字节对齐(Alignment),以实现在对象内存中存取更有效率。字节对齐就是将数值调整到某数的整数倍,在32位计算机中,通常Alignment为4bytes,以使bus的“运输量”达到最高效率。

可以看出,派生类D对象在内存中占有 44 个字节。

 重复继承中的C++对象内部模型 用图片表示如下:

从图中可以看出,在派生类D中,存在着两份基类B的成员实例,分别为ib和cb,所以在 C++对象的内存布局(下) 指出这样可能会出现二义性编译错误。我们可以指定类作用域符::进行限定来消除二义性,也可以在语言层面利用虚拟继承机制来解决。

二、钻石型多重虚拟继承

 在《深度探索C++对象模型》中提到:一个virtual base class subobject只会在derived class中存在一份实体,不管它在class继承体系中出现多少次!

因此,虚拟继承的就是为了解决重复继承中多个间接父类的问题。钻石型的结构就是最经典的虚拟多重继承结构。

UML类图如下:

如上图,让B1和B2各自维护的一个B子对象,折叠成一个由D维护的单一的B子对象,并且还可以保存基类和派生类的指针之间的多态指定操作,这对于编译器实现来说,难度非常高。 《深度探索C++对象模型》提到 一般的实现方法如下所述: 将D对象分割为两部分,一个不变局部和一个共享局部。 不变局部中的数据,不管后继如何衍化,总是拥有固定的偏移量,所以这一部分数据可以被直接存取,至于共享局部,所表现的就是虚拟继承的基类子对象,这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们是间接存取。

所以,一般的布局策略是安排好派生类对象的不变部分,然后再建立其共享部分。在接下来的分析可以看出,VC++编译器实现中,在每一个派生类对象中插入一些指针 vbptr ,每个指针指向一个虚拟继承的基类子对象。要存取继承得来的基类子对象,可以使用相关指针间接完成。

要实现虚拟继承,我们只需要在B1和B2继承B的语法中加入 virtual关键字 即可。实现代码如下:

  View Code

使用cl编译器查看钻石型虚拟重复继承中的C++对象内存模型结果如下图所示:

从上图可以看出,虚拟重复继承中的派生类D对象在内存中占有52字节,比之前多了8个字节。

 虚拟重复继承中的C++对象内部模型 用图片表示如下:

从图中可以看出,VC++编译器在实现虚拟继承时,在派生类的对象中安插了两个 vbptr 指针。因此,对每个继承自虚基类的类实例,将增加一个隐藏的“虚基类表指针”(vbptr)成员变量,从而达到 间接计算虚基类位置 的目的。该变量指向一个全类共享的偏移量表,表中项目 记录了对于该类而言,“虚基类表指针”与虚基类之间的偏移量。由上可以看出,B1虚基类表指针vbptr与虚基类B之间的偏移量是40字节,B2 虚基类表指针vbptr与虚基类B之间的偏移量是24字节。第一项中-4的含义:表示的是vptr和vbptr的距离,如果B1中没有虚函数的定义,这个地方就会是0。 vbptr就是存放在vptr下面的位置。

我们注意到在虚拟继承的C++对象内存布局中,还有一个4个字节的vtordisp字段,vtordisp在 MSDN中这样解释 :

Enables the addition of the hidden vtordisp construction/destruction displacement member. The  vtordisp  pragma is applicable only to code that uses virtual bases. If a derived class overrides a virtual function that it inherits from a virtual base class, and if a constructor or destructor for the derived class calls that function using a pointer to the virtual base class, the compiler may introduce additional hidden “vtordisp” fields into classes with virtual bases.

也就是说如果虚拟 继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加 vtordisp 域。

但在本例中, vtordisp为什么存在于C++派生类对象中,对象如何使用它,我却不得而知,希望向大家请教。

至此,我们已经分析完在VC++编译器实现中的重复继承和钻石型虚拟重复继承的C++对象内存模型。这篇博客也花了大概4个小时,时间挺久,但非常值得,希望大家多多指教。

 转载和分享请注明出处。 http://www.cnblogs.com/liu-jun/archive/2013/05/17/3083736.html

当你心中只有一个目标时,全世界都会给你让路!Read more! Write more! Practise more! 新浪微博:liu_军

 

分类:  探索C++

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于虚拟多重继承的C++对象内存模型的详细内容...

  阅读:42次