虚继承的内存模型分析

简介: 这里就先从以下几个点进行说明吧:  虚继承和虚基类 vs2010下的cl命令 内存模型      虚继承和虚基类      虚继承:在继承定义中包含了virtual关键字的继承关系;      虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:class CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系中的基类。

 

这里就先从以下几个点进行说明吧:

  •  虚继承和虚基类
  • vs2010下的cl命令
  • 内存模型

     虚继承和虚基类

     虚继承:在继承定义中包含了virtual关键字的继承关系;
     虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:class CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系中的基类。

     vs2010下的cl命令

     微软的VS2010提供了一个新的选项,给用户显示C++对象在内存中的布局。这个选项就是:

  1. /d1reportSingleClassLayout  

具体使用方法如下,在写好相应的cpp文件之后,需要启动VS2010的命令行工具“Visual Studio 2010Command Prompt”,切换到cpp文件所在目录之后,输入如下的命令:

  1. cl [filename].cpp /d1reportSingleClassLayout[className]  

cl当然就是MS的编译器;[filename].cpp就是你所想要查看的class所在的cpp文件(class定义在头文件也没关系,还是只要编译cpp文件即可);而你需要在最后加上[className],也就是你需要查看的class的类名。

【举例】test.cpp文件代码如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:  
  7.     int a;  
  8.     virtual void fcn() {};  
  9. };  
  10.   
  11. class Derived : public Base  
  12. {  
  13. public:  
  14.     virtual void fcn2() {};  
  15. private:  
  16.     int d;  
  17.     void fcn3() { }  
  18. };  
  19.   
  20. int main()   
  21. {  
  22. }  

查看Derived这个类的对象在内存中的布局,那么就可以用下面的命令行:

  1. cl Test.cpp /d1reportSingleClassLayoutDerived  

结果显示如下:


       可以看到class Derived的对象的内存布局,在派生类对象的开始包含了基类Base的对象,其中有一个虚表指针,指向的就是下面的 Derived::$vftable@ (virtual function table),表中包含了Derived类中所有的虚函数。

      内存模型

     在这一小节里面我主要从他面试的几个题目中来谈谈虚继承的内存模型。

     代码一: 

  1. class A  
  2. {  
  3.     virtual void a()  
  4.     {  
  5.     }  
  6. };  
  7. class A1  
  8. {  
  9.     virtual void a()  
  10.     {  
  11.     }  
  12. };  
  13.   
  14. class B : public A , virtual public A1  
  15. {  
  16. };  
  17. void main()  
  18. {  
  19.     cout<<"sizeof A: "<<sizeof(A)<<endl;  
  20.     cout<<"sizeof A1: "<<sizeof(A1)<<endl;  
  21.     cout<<"sizeof B: "<<sizeof(B)<<endl;     //   
  22. }  

输出结果是:

在命令行中输入:

  1. cl test.cpp /d1reportSingleClassLayoutB  

         从这个内存布局就可以看出来class A、class A1和ClassB的大小,本身class A的大小应该是1bytes的内存定位大小加上虚函数指针4bytes因为有了虚函数指针后1bytes的占 位就可以取消了。所以A的大小就是4bytes,同理Class A1。对于Class B它主要是从class A和class A1(虚继承)而来,所以B里面包含有一个A和A1同时因为是虚继承所以就有一个指向虚基类(A1)的vbptr指针。这里为了方便我做个图直观一点:


        所以说class B的大小是12bytes

代码二:

  1. class A  
  2. {  
  3.     int a;  
  4. };  
  5. class B  
  6. {  
  7.     int b;  
  8. };  
  9.   
  10. class C  
  11. {  
  12. };  
  13. class D  
  14. {  
  15. };  
  16. class E: public virtual A , public virtual B , public virtual C , public virtual D  
  17. {  
  18. };  
  19. void main()  
  20. {  
  21.     cout<<"sizeof E: "<<sizeof(E)<<endl;      //   
  22. }  

       Class 如果内含一个或多个virtual base class subobjects,将被分割为两部分:一个不变局部和一个共享局部.不变局部中的数据,不管后继如何衍化,总是拥有固定的 offset(从object的开头算起),所以这一部分数据可以被直接存取。至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们只可以被间接存取。各家编译器实现技术之间的差异就在于间接存取的方 法不同。一般的布局策略是先安排好derived class的不变部分,然后再建立其共享部分.然而,这中间存在着一个问题:如何能够存取Class的共享部分呢?我这就从微软的编译器来看它的内存布局。

        上面代码二输出的结果:


class E的大小是不是有点怪,在命令行中输入:

  1. cl test.cpp /d1reportSingleClassLayoutE  

           如前面黑色字体标注一样,因为classE是多重虚继承,所以在内存中的布局是分为固定局部和共享局部,固定局部的大小就int a,b 所以是8 bytes。我的上一篇博文(VC++对象布局的奥秘:虚函数、多继承、虚拟继承)对这个内存某型有个大概的讲解,在这就不多言了。

  1. E::$vbtable@:  
  2. 1>   0   | 0  
  3. 1>   1   | 4 (Ed(E+0)A)  
  4. 1>   2   | 8 (Ed(E+0)B)  
  5. 1>   3   | 12 (Ed(E+0)C)  
  6. 1>   4   | 16 (Ed(E+0)D)  

        一般都是把vbtable放在对象的前面,所以vbtable(virtual base class table)与对象首地址的偏移量一般就是中间隔着vbtable,这个地方的4表示E的vbtable与虚基类A首地址的偏移量,同理,8,12,16这个就不用我说了。既然这都给出了vbtable域虚基类的地址偏移量了,说明在E对象的内存中还是存在分配的空间。


          从vs2010的内存来看的确也是分配了,通过前面的vbtable的偏移来看对象的大小就是16bytes(a是class A的首地址,b是classB的首地址)。有人会问为什么不是20bytes?上面的内存分配偏移是16就说明有20bytes。偏移16,刚好在变量b 的下面分配4bytes的C(这说的不准确,不过好明白)这样偏移是16的地方就是D,vbtable现在可以正常定位,Class D为空,就没有必要再分配4bytes的空间,所以sizeof E的大小应该是:vbtable指针(4bytes) +固定局部(a,b工8bytes)+ C 的4bytes(这有点不好明白,不过下面我还有例子,加深理解)。

代码三:

 

  1. class A  
  2. {  
  3. };  
  4. class B  
  5. {  
  6.     int b;  
  7. };  
  8.   
  9. class C  
  10. {  
  11. };  
  12. class D  
  13. {  
  14. };  
  15. class E: public virtual A , public virtual B , public virtual C , public virtual D  
  16. {  
  17. };  
  18. void main()  
  19. {  
  20.    cout<<"sizeof A: "<<sizeof(A)<<endl;  
  21.    cout<<"sizeof B: "<<sizeof(B)<<endl;  
  22.    cout<<"sizeof C: "<<sizeof(C)<<endl;  
  23.    cout<<"sizeof D: "<<sizeof(D)<<endl;  
  24.    cout<<"sizeof E: "<<sizeof(E)<<endl;      //   
  25. }  

上面代码二输出的结果:

这回感觉还是很奇怪吧,看看内存布局吧。

        首先,看看vbtable把,是不是很奇怪,为什么A,B的偏移都是4,ClassA本身就是空,刚好ClassB有一个成员所以肯定需要给classB 分配内存的,所以这样就可以找到A的偏移,Class C和Class D就没办法了没办法计算偏移,所以就给ClassC分配了内存,ClassC有了内存这样D就可以的偏移也就出来了,这时候就有人问为什么ClassD的 偏移为什么不是8呢(说实话我也没搞懂,我的猜想是对象本身为空,但是为了内存对齐所以就是4bytes。希望懂的大神给以指点,我通过调试多个例子,得 出来的,下面的例子就就更好的说明了这点),

代码四:

  1. class A  
  2. {  
  3. };  
  4. class B  
  5. {     
  6. };  
  7.   
  8. class C  
  9. {  
  10. };  
  11. class D  
  12. {  
  13. };  
  14. class E: public virtual A , public virtual B , public virtual C , public virtual D  
  15. {  
  16. };  
  17. void main()  
  18. {  
  19.     E ee;  
  20.     cout<<"sizeof A: "<<sizeof(A)<<endl;  
  21.     cout<<"sizeof B: "<<sizeof(B)<<endl;  
  22.     cout<<"sizeof C: "<<sizeof(C)<<endl;  
  23.     cout<<"sizeof D: "<<sizeof(D)<<endl;  
  24.     cout<<"sizeof E: "<<sizeof(E)<<endl;      //   
  25. }  

输出结果是:

再看看内存模型吧


         从vbtable来看,里面存储的偏移class E的大小默认固定布局是4bytes,共享布局里面classA、class B和classC的大小是12bytes。所以就是16bytes。

来源:http://blog.csdn.net/wangqiulin123456/article/details/8059536

img_e00999465d1c2c1b02df587a3ec9c13d.jpg
微信公众号: 猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

目录
相关文章
|
3月前
|
存储 C++
|
7月前
|
Java 编译器 索引
|
1月前
|
存储 安全 编译器
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
31 0
|
3月前
|
编译器 C++
VS2022查看类内存布局
先右键点击属性, 选择左侧的C/C++==>命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局。切切注意, Layout跟指定的结构/类名CTest之间没有空格, 有空格就不对了. 这会只输出指定的结构的内存布局.这个开关输出所有类, 主要是一大堆编译器内部的结构的内存布局, 其实还有一个开关是。
53 0
|
安全 Java
Java多线程对于成员变量和局部变量的影响
Java多线程对于成员变量和局部变量的影响
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
416 0
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
260 1
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
|
程序员 数据安全/隐私保护 C++
C++的虚拟继承(基础)
C++的虚拟继承(基础)
134 2
C++的虚拟继承(基础)
|
编译器 C++
C++中继承与虚继承本质之优秀
C++中继承与虚继承本质之优秀
121 0
C++中继承与虚继承本质之优秀
|
缓存 Java 编译器
深入分析java内存模型(注意和java内存结构的区别)
最近在更java多线程相关的文章,正好有人问我一些java内存模型的问题,因此花了一些时间,好好地了解一下。本篇文章主要是为了解决以下几个问题? 1、java内存模型和java内存结构有什么区别? 2、为什么要有内存模型? 3、java的内存模型是什么样子的? 这篇文章,基本上不会涉及到代码,全是一些概念性的知识,但是也是面试常问和java进阶所需要掌握的必要的基本知识点,所以,希望你耐着性子,慢慢来。
379 3
深入分析java内存模型(注意和java内存结构的区别)