C++之虚函数、虚函数表的内存分布

来源:互联网 发布:水果软件虚拟键盘 编辑:程序博客网 时间:2024/06/17 03:12
使用Visual Studio工具来看是类的内存分布

 

 

先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。

下面可以定义一个类,像下面这样:

 
[cpp] view plain copy
  1. class Base  
  2. {  
  3.     int a;  
  4.     int b;  
  5. public:  
  6.     void CommonFunction();  
  7. };  

然后编译一下,可以看到输出框里面有这样的排布:

这里不想花精力在内存对齐因素上,所以成员变量都设为int型。

从这里可以看到普通类的排布方式,成员变量依据声明的顺序进行排列(类内偏移为0开始),成员函数不占内存空间。

 

再看下继承,往后面添加如下代码:

 
[cpp] view plain copy
  1. class DerivedClass: public Base  
  2. {  
  3.     int c;  
  4. public:  
  5.     void DerivedCommonFunction();  
  6. };  

编译,然后看到如下的内存分布(父类的内存分布不变,这里只讨论子类成员变量的内存分布):

可以看到子类继承了父类的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节。

 

下面给基类加上虚函数,暂时注释掉DerivedClass,看一下这时的内存排布:

 
[cpp] view plain copy
  1. class Base  
  2. {  
  3.     int a;  
  4.     int b;  
  5. public:  
  6.     void CommonFunction();  
  7.     void virtual VirtualFunction();  
  8. };  

这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。

编译器是在构造函数创建这个虚表指针以及虚表的。

那么编译器是如何利用虚表指针与虚表来实现多态的呢?是这样的,当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。

所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p->VirtualFunction,实际上是p->vfptr->VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。

 

下面加上子类,并在子类中添加虚函数,像下面这样:

 
[cpp] view plain copy
  1. class DerivedClass: public Base  
  2. {  
  3.     int c;  
  4. public:  
  5.     void DerivedCommonFunction();  
  6.     void virtual VirtualFunction();  
  7. };  

可以看到子类内存的排布如下:

上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,最后是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。

 

我们把子类换个代码,像这样:

 
[cpp] view plain copy
  1. class DerivedClass1 : public Base  
  2. {  
  3.     int c;  
  4. public:  
  5.     void DerivedCommonFunction();  
  6.     void virtual VirtualFunction2();  
  7. };  

注意到这时我们并没有覆写父类的虚方法,而是重声明了一个新的子类虚方法,内存分布如下:

还是只有一个虚表指针,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction2,那么会从子类中寻找对应的虚函数。

 

我们再改造一下子类,像这样:

 
[cpp] view plain copy
  1. class DerivedClass1 : public Base  
  2. {  
  3.     int c;  
  4. public:  
  5.     void DerivedCommonFunction();  
  6.     void virtual VirtualFunction();  
  7.     void virtual VirtualFunction2();  
  8. };  

我们既覆写父类的虚函数,也有新添的虚函数,那么可以料想的到,是下面的这种内存分布:

下面来讨论多重继承,代码如下:

[cpp] view plain copy
  1. class Base  
  2. {  
  3.     int a;  
  4.     int b;  
  5. public:  
  6.     void CommonFunction();  
  7.     void virtual VirtualFunction();  
  8. };  
  9.   
  10.   
  11. class DerivedClass1: public Base  
  12. {  
  13.     int c;  
  14. public:  
  15.     void DerivedCommonFunction();  
  16.     void virtual VirtualFunction();  
  17. };  
  18.   
  19. class DerivedClass2 : public Base  
  20. {  
  21.     int d;  
  22. public:  
  23.     void DerivedCommonFunction();  
  24.     void virtual VirtualFunction();  
  25. };  
  26.   
  27. class DerivedDerivedClass : public DerivedClass1, public DerivedClass2  
  28. {  
  29.     int e;  
  30. public:  
  31.     void DerivedDerivedCommonFunction();  
  32.     void virtual VirtualFunction();  
  33. };  

内存分布从父类到子类,依次如下:

Base中有一个虚表指针,地址偏移为0

 

DerivedClass1继承了Base,内存排布是先父类后子类。

DerivedClass2的情况是类似于DerivedClass1的。

 

下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。

这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。

 

如果采用虚继承,像下面这样:

 
[cpp] view plain copy
  1. class DerivedClass1: virtual public Base  
  2. {  
  3.     int c;  
  4. public:  
  5.     void DerivedCommonFunction();  
  6.     void virtual VirtualFunction();  
  7. };  
  8.   
  9. class DerivedClass2 : virtual public Base  
  10. {  
  11.     int d;  
  12. public:  
  13.     void DerivedCommonFunction();  
  14.     void virtual VirtualFunction();  
  15. };  
  16.   
  17. class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2  
  18. {  
  19.     int e;  
  20. public:  
  21.     void DerivedDerivedCommonFunction();  
  22.     void virtual VirtualFunction();  
  23. };  

Base类没有变化,但往下看:

DerivedClass1就已经有变化了,原来是先排虚表指针与Base成员变量,vfptr位于0地址偏移处;但现在有两个虚表指针了,一个是vbptr,另一个是vfptr。vbptr是这个DerivedClass1对应的虚表指针,它指向DerivedClass1的虚表vbtable,另一个vfptr是虚基类表对应的虚指针,它指向vftable。

下面列出了两张虚表,第一张表是vbptr指向的表,8表示{vbptr}与{vfptr}的偏移;第二张表是vfptr指向的表,-8指明了这张表所对应的虚指针位于内存的偏移量。

DerivedClass2的内存分布类似于DerivedClass1,同样会有两个虚指针,分别指向两张虚表(第二张是虚基类表)。

下面来仔细看一下DerivedDerivedClass的内存分布,这里面有三个虚指针了,但base却只有一份。第一张虚表是内含DerivedClass1的,20表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,第二张虚表是内含DerivedClass2的,12表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,最后一张表是虚基表,-20指明了它对应的虚指针{vfptr}在内存中的偏移。

 

看下面的一段继承方式和上面的区别

[cpp] view plain copy
  1. class DerivedDerivedClass : virtual public DerivedClass1, virtual public DerivedClass2  
  2. {  
  3.     int e;  
  4. public:  
  5.     void DerivedDerivedCommonFunction();  
  6.     void virtual VirtualFunction();  
  7. };  

[cpp] view plain copy
  1. cout<<sizeof(Base)<<endl;  
  2. cout<<sizeof(DerivedClass1)<<endl;  
  3. cout<<sizeof(DerivedClass2)<<endl;  
  4. cout<<sizeof(DerivedDerivedClass)<<endl;  
12

20

20

36

虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。

下面总结一下(当基类有虚函数时):

1. 每个类都有虚指针和虚表;

2. 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;

3. 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。

最后,将上述五种类的构造都展示出来

[cpp] view plain copy
  1. class Base  size(12):  
  2. 1>   +---  
  3. 1>   0   | {vfptr}  
  4. 1>   4   | a  
  5. 1>   8   | b  
  6. 1>   +---  
  7. 1>    
  8. 1>  Base::$vftable@:  
  9. 1>   | &Base_meta  
  10. 1>   |  0  
  11. 1>   0   | &Base::VirtualFunction  
  12. 1>    
  13. 1>  Base::VirtualFunction this adjustor: 0  
  14. 1>  class DerivedDerivedClass    size(32):  
  15. 1>   +---  
  16. 1>   | +--- (base class DerivedClass1)  
  17. 1>   0   | | {vbptr}  
  18. 1>   4   | | c  
  19. 1>   | +---  
  20. 1>   | +--- (base class DerivedClass2)  
  21. 1>   8   | | {vbptr}  
  22. 1>  12   | | d  
  23. 1>   | +---  
  24. 1>  16   | e  
  25. 1>   +---  
  26. 1>   +--- (virtual base Base)  
  27. 1>  20   | {vfptr}  
  28. 1>  24   | a  
  29. 1>  28   | b  
  30. 1>   +---  
  31. 1>    
  32. 1>  DerivedDerivedClass::$vbtable@DerivedClass1@:  
  33. 1>   0   | 0  
  34. 1>   1   | 20 (DerivedDerivedClassd(DerivedClass1+0)Base)  
  35. 1>    
  36. 1>  DerivedDerivedClass::$vbtable@DerivedClass2@:  
  37. 1>   0   | 0  
  38. 1>   1   | 12 (DerivedDerivedClassd(DerivedClass2+0)Base)  
  39. 1>    
  40. 1>  DerivedDerivedClass::$vftable@:  
  41. 1>   | -20  
  42. 1>   0   | &DerivedDerivedClass::VirtualFunction  
  43. 1>    
  44. 1>  DerivedDerivedClass::VirtualFunction this adjustor: 20  
  45. 1>    
  46. 1>  vbi:    class  offset o.vbptr  o.vbte fVtorDisp  
  47. 1>              Base      20       0       4 0  
  48. 1>    
  49. 1>    
  50. 1>  class DerivedDerivedClasss   size(36):  
  51. 1>   +---  
  52. 1>   0   | {vbptr}  
  53. 1>   4   | e  
  54. 1>   +---  
  55. 1>   +--- (virtual base Base)  
  56. 1>   8   | {vfptr}  
  57. 1>  12   | a  
  58. 1>  16   | b  
  59. 1>   +---  
  60. 1>   +--- (virtual base DerivedClass1)  
  61. 1>  20   | {vbptr}  
  62. 1>  24   | c  
  63. 1>   +---  
  64. 1>   +--- (virtual base DerivedClass2)  
  65. 1>  28   | {vbptr}  
  66. 1>  32   | d  
  67. 1>   +---  
  68. 1>    
  69. 1>  DerivedDerivedClasss::$vbtable@DerivedDerivedClasss@:  
  70. 1>   0   | 0  
  71. 1>   1   | 8 (DerivedDerivedClasssd(DerivedDerivedClasss+0)Base)  
  72. 1>   2   | 20 (DerivedDerivedClasssd(DerivedDerivedClasss+0)DerivedClass1)  
  73. 1>   3   | 28 (DerivedDerivedClasssd(DerivedDerivedClasss+0)DerivedClass2)  
  74. 1>    
  75. 1>  DerivedDerivedClasss::$vftable@:  
  76. 1>   | -8  
  77. 1>   0   | &DerivedDerivedClasss::VirtualFunction  
  78. 1>    
  79. 1>  DerivedDerivedClasss::$vbtable@DerivedClass1@:  
  80. 1>   0   | 0  
  81. 1>   1   | -12 (DerivedDerivedClasssd(DerivedClass1+0)Base)  
  82. 1>    
  83. 1>  DerivedDerivedClasss::$vbtable@DerivedClass2@:  
  84. 1>   0   | 0  
  85. 1>   1   | -20 (DerivedDerivedClasssd(DerivedClass2+0)Base)  
  86. 1>    
  87. 1>  DerivedDerivedClasss::VirtualFunction this adjustor: 8  
  88. 1>    
  89. 1>  vbi:    class  offset o.vbptr  o.vbte fVtorDisp  
  90. 1>              Base       8       0       4 0  
  91. 1>     DerivedClass1      20       0       8 0  
  92. 1>     DerivedClass2      28       0      12 0  


转载自http://blog.csdn.net/yusiguyuan/article/details/38764661