C++虚继承的内存模型

来源:互联网 发布:mysql ip数据库 编辑:程序博客网 时间:2024/05/16 08:03

http://blog.csdn.net/a627088424/article/details/47999757#comments


[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3. class ZooAnimal  
  4. {  
  5. public:  
  6.       virtual void print()  
  7.       {  
  8.            cout << "run: ZooAnimal::print()" <<endl;  
  9.       }  
  10.       void run(int whichTable, int whichFunc)  
  11.       {  
  12.           //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表  
  13.           void (***func)(void)=reinterpret_cast<void (***)(void)>(this);  
  14.           (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数  
  15.       }  
  16.       int a;  
  17. };  
  18.    
  19. class Bear: public ZooAnimal  
  20. {  
  21. public:  
  22.       void run(int whichTable, int whichFunc)  
  23.       {  
  24.            //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表  
  25.            void (***func)(void)=reinterpret_cast<void (***)(void)>(this);  
  26.            (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数  
  27.       }  
  28.        
  29.       virtual void dis()  
  30.       {  
  31.            cout << "run: Bear::dis()" <<endl;  
  32.       }  
  33.       void dis(char)  
  34.       {  
  35.            cout << "run: Bear::dis(char)" <<endl;  
  36.       }  
  37.      virtual void dis(int)  
  38.       {  
  39.            cout << "run: Bear::dis(int)" <<endl;  
  40.       }  
  41.       int b;  
  42. };  
  43.    
  44.    
  45. int main()  
  46. {  
  47.        
  48.       Bear b;  
  49.        
  50.       ZooAnimal& a=static_cast<ZooAnimal&>(b);  
  51.       cout << "ZooAnimal:" <<endl;  
  52.       cout << "&a=" << &a <<endl;  
  53.       cout << "&a.a=" << &a.a <<endl;  
  54.       cout<<endl;  
  55.        
  56.       a.run(0,0);  
  57.       a.run(0,1);  
  58.       a.run(0,2);  
  59.       cout<<endl;  
  60.        
  61.       cout << "Bear:" <<endl;  
  62.       cout << "&b=" << &b <<endl;  
  63.       cout << "&b.a=" << &b.a <<endl;  
  64.       cout << "&b.b=" << &b.b <<endl;  
  65.       cout<<endl;  
  66.        
  67.       b.run(0,0);  
  68.       b.run(0,1);  
  69.       b.run(0,2);  
  70.       cout<<endl;  
  71.    
  72.       cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal)  <<endl;  
  73.       cout << "sizeof(Bear)=" << sizeof(Bear) << endl;  
  74. }  

[cpp] view plain copy
  1.   

平台Ubantu,g++ 4.8

结果:

ZooAnimal:

&a=0xbf859914

&a.a=0xbf859918

 

run: ZooAnimal::print()

run: Bear::dis()

run: Bear::dis(int)

 

 

Bear:

&b=0xbf859914

&b.a=0xbf859918

&b.b=0xbf85991c

 

run: ZooAnimal::print()

run: Bear::dis()

run: Bear::dis(int)

 

sizeof(ZooAnimal)=8

sizeof(Bear)=12

Bear对象,相对于Animal对象,新增了b变量。没有为Bear的新的虚函数增加新vfptr,而是将Bear的新的虚函数地址,在原来vfptr指向的虚函数表中尾增。

我们接下来让Bear虚继承自ZooAnimal,class Bear: virtual public ZooAnimal

重新编译,运行

ZooAnimal:

&a=0xbfb77688

&a.a=0xbf7768c

 

run: ZooAnimal::print()

段错误(核心已转储)

 

将main函数中a.run(0,1),a.run(0,2)注释的,编译运行:

ZooAnimal:

&a=0xbfe99ad8

&a.a=0xbfe99adc

 

run: ZooAnimal::print()

 

Bear:

&b=0xbfe99ad0

&b.a=0xbfe99adc

&b.b=0xbfe99ad4

 

run: Bear::dis()

run: Bear::dis(int)

段错误(核心已转储)

将main中的b.run(0,2)注释掉,编译运行:

ZooAnimal:

&a=0xbfe99ad8

&a.a=0xbfe99adc

 

run: ZooAnimal::print()

 

Bear:

&b=0xbfe99ad0

&b.a=0xbfe99adc

&b.b=0xbfe99ad4

 

run: Bear::dis()

run: Bear::dis(int)

 

sizeof(ZooAnimal)=9

sizeof(Bear)=16

通过上面的过程及最终的结果,可以推测出Bear的内存布局:

可见派生类Bear并没有使用基类ZooAnimal的vfptr,而是单独为自己开辟了一个vfptr。通过a,b的位置变化,看出ZooAnimal被放到尾部了。

继续,派生Panda。

 

[cpp] view plain copy
  1. class Panda: public Bear  
  2. {  
  3. public:  
  4.       void run(int whichTable, int whichFunc)  
  5.       {  
  6.            //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表  
  7.            void (***func)(void)=reinterpret_cast<void (***)(void)>(this);  
  8.            (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数  
  9.       }  
  10.       virtual void test()  
  11.       {  
  12.            cout << "run: Panda::test()" <<endl;  
  13.       }  
  14.        
  15.       int t;  
  16. };  
  17.    
  18. int main()  
  19. {  
  20.       Panda b;  
  21.                   
  22.       ZooAnimal& a=static_cast<ZooAnimal&>(b);  
  23.       cout << "ZooAnimal:" <<endl;  
  24.       cout << "&a=" << &a <<endl;  
  25.       cout << "&a.a=" << &a.a <<endl;  
  26.       cout<<endl;  
  27.        
  28.       a.run(0,0);  
  29.       //a.run(0,1);段错误  
  30.       //a.run(0,2);段错误  
  31.       //a.run(0,3);段错误  
  32.       cout<<endl;  
  33.        
  34.       Bear& c=static_cast<Bear&>(b);  
  35.       cout << "Bear:" <<endl;  
  36.       cout << "&c=" << &c <<endl;  
  37.       cout << "&c.a=" << &c.a <<endl;  
  38.       cout << "&c.b=" << &c.b <<endl;  
  39.       cout << endl;  
  40.        
  41.       c.run(0,0);  
  42.       c.run(0,1);  
  43.       c.run(0,2);  
  44.       //c.run(0,3);段错误  
  45.    
  46.       cout<<endl;  
  47.        
  48.       cout << "Panda:" <<endl;  
  49.       cout << "&b=" << &b <<endl;  
  50.       cout << "&b.a=" << &b.a <<endl;  
  51.       cout << "&b.b=" << &b.b <<endl;  
  52.       cout << "&b.t=" << &b.t <<endl;  
  53.       cout<<endl;  
  54.        
  55.       b.run(0,0);  
  56.       b.run(0,1);  
  57.       b.run(0,2);  
  58.       //b.run(0,3);段错误  
  59.       cout <<endl;  
  60.        
  61.    
  62.       cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal)  <<endl;  
  63.       cout << "sizeof(Bear)=" << sizeof(Bear) << endl;  
  64.       cout << "sizeof(Panda)=" << sizeof(Panda) << endl;  
  65. }  
结果:


根据结果推测内存模型


虚继承的祖父类仍被放到尾部,并具有自己的独立的vfptr。而孙子类Panda非虚继承父类Bear,采取的方法也仍是只有一个vfptr,并将自己新的虚函数的地址尾加到表中。

继续,将其扩展成封闭的菱形继承:


添加了Raccon类,该类不包含虚函数,有一个成员变量int r。

[cpp] view plain copy
  1. class Raccon:virtual public ZooAnimal{   
  2. public:  
  3.       void run(int whichTable, int whichFunc)  
  4.       {  
  5.            //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表  
  6.            void (***func)(void)=reinterpret_cast<void (***)(void)>(this);  
  7.            (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数  
  8.       }  
  9.       int r;  
  10. };  

[cpp] view plain copy
  1. int main()  
  2. {  
  3.       Panda b;  
  4.                   
  5.       ZooAnimal& a=static_cast<ZooAnimal&>(b);  
  6.       cout << "ZooAnimal:" <<endl;  
  7.       cout << "&a=" << &a <<endl;  
  8.       cout << "&a.a=" << &a.a <<endl;  
  9.       cout<<endl;  
  10.        
  11.       a.run(0,0);  
  12.       //a.run(0,1);//段错误  
  13.       //a.run(0,2);//段错误  
  14.       //a.run(0,3);//段错误  
  15.       cout<<endl;  
  16.        
  17.       Bear& c=static_cast<Bear&>(b);  
  18.       cout << "Bear:" <<endl;  
  19.       cout << "&c=" << &c <<endl;  
  20.       cout << "&c.a=" << &c.a <<endl;  
  21.       cout << "&c.b=" << &c.b <<endl;  
  22.       cout << endl;  
  23.        
  24.       c.run(0,0);  
  25.       c.run(0,1);  
  26.       c.run(0,2);  
  27.       //c.run(0,3);//段错误  
  28.    
  29.       cout<<endl;  
  30.        
  31.       Raccon& d=static_cast<Raccon&>(b);  
  32.       cout << "Raccon:" <<endl;  
  33.       cout << "&d=" << &d <<endl;  
  34.       cout << "&d.a=" << &d.a <<endl;  
  35.       cout << "&d.r=" << &d.r <<endl;  
  36.       cout << endl;  
  37.        
  38.       //d.run(0,0);段错误  
  39.       //d.run(0,1);段错误  
  40.       //d.run(0,2);段错误  
  41.       //c.run(0,3);段错误  
  42.    
  43.       cout<<endl;  
  44.        
  45.       cout << "Panda:" <<endl;  
  46.       cout << "&b=" << &b <<endl;  
  47.       cout << "&b.a=" << &b.a <<endl;  
  48.       cout << "&b.b=" << &b.b <<endl;  
  49.       cout << "&b.r=" << &b.r <<endl;  
  50.       cout << "&b.t=" << &b.t <<endl;  
  51.       cout<<endl;  
  52.        
  53.       b.run(0,0);  
  54.       b.run(0,1);  
  55.       b.run(0,2);  
  56.       //b.run(0,3);//段错误  
  57.       cout <<endl;  
  58.        
  59.    
  60.       cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal)  <<endl;  
  61.       cout << "sizeof(Bear)=" << sizeof(Bear) << endl;  
  62.       cout << "sizeof(Raccon)=" << sizeof(Raccon) << endl;  
  63.       cout << "sizeof(Panda)=" << sizeof(Panda) << endl;  
  64. }  

 



Panda从直接基类Bear和Racoon分别继承了vfptr,并将自己的新的虚函数指针,尾加到了第一个直接基类Bear的vftable中。而虚继承而来的祖类ZooAnimal仍就整体放到了Panda尾部。特别注意下vfptr$Raccon。笔者实践的时候,填加了函数用于访问虚函数表中的函数指针的值,虚函数表尾部确实都为0,应该是用于标识虚函数表结束。如果对祖类ZooAnimal的继承不是虚继承,Panda的内存布局,根据上述的原则,而是很好推断的,以前从ZooAnimal虚继承而来的类,都是要自己单独再分配自己的vfptr,现在可以自己重用基类的了,则Bear将减少一个指针的内存大小,即sizeof(Bear)=12,同理sizeof(Raccon)=12,而Panda虽然不用单独分配自己的vfptr,但是由于不是虚继承,对于ZooAnimal的成员变量int a,将有两份拷贝,而已sizeof(Panda)仍是28.

      在网上搜了下资料后自己有时间代码实践验证下。对比网上其他的,对于虚继承跟别人得到的结果又出入。其他人的结果大都虚继承后有vbptr,即虚基类偏移量指针。比如这篇文章:点击打开链接

笔者,将其代码写入自己的平台验证,最后孙子类的大小是28而不是文章说的36。他的孙子类出现了两个vbptr。他使用的是vs2010.

      编译器实现有关?

      将代码拷贝到vs2012中运行,其使用的是微软自家的cl编译器。

      由于运行的时候run函数中的func函数指针为void  (***)(void),在通过其运行Bear的virtual  void dis(int)时,编译器出错,栈出了问题,问了方便分析,直接将该函数注释掉。根据最后的输出结果,分析得到如下内存分布:

     

sizeof(ZooAnimal)=8;

sizeof(Bear)=20;

sizeof(Raccon)=16;

sizeof(Panda)=32;

特别注意,没有为Raccon生成vfptr。虚基类偏移表中,第一项应该是具有一定的标识意义,猜测可能是vbptr相对于对象头部的偏移量,而第二项保存的是虚基类相对于vbptr的偏移量。Raccon类中并没有虚函数,尽管他虚继承的ZooAnimal中有虚函数,并没有为Raccon生成用于保存虚函数的vfptr。我试着给Raccon添加了一个虚函数virtual void  vs2012test(),结果编译器为Rccon分配了vfptr,并且vbptr$Rccon偏移表第一个变为-4


sizeof(ZooAnimal)=8;

sizeof(Bear)=20;

sizeof(Raccon)=20;

sizeof(Panda)=36;

 

 

虚继承的内存布局与编译器相关!!

g++原则更为简单,将虚基类内存(包括vfptr)尾加到派生类。如果虚基类有虚函数,派生类一定会有自己的vfptr,如果派生类没有虚函数,该vfptr指向空。

cl虚继承时,同样将虚基类内存(包括vfptr)尾加到派生类,并且派生类将产生vbptr,其中存放了虚基类对象在派生类对象中的偏移量。派生类对象中有无vfptr与虚基类无关,仅当派生类有虚函数时,才会为其分配vfptr。

 

编译器是如何实现对虚基类的访问?这里暂时不继续挖了,越挖越深,洗洗睡了~

[cpp] view plain copy
  1. void run(int whichTable, int whichFunc)  
  2. {  
  3.      //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表  
  4.      void (***func)(void)=reinterpret_cast<void (***)(void)>(this);  
  5.      (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数  
  6. }  

原创粉丝点击