深入理解C++对象模型-对象的内存布局,vptr,vtable

前言:本文将用到另一篇文章所提供的类模板类ReinterpretCast,详细请参考文章<<深入理解C++对象模型之类型转换:ReinterpretCast>>

 vtpr的位置:
为了支持多态,C++引入了vtpr和vtable这两个概念.对于每个有虚函数的类,C++都会为其生成一个vtable,并在类中添加一个隐含的数据成员vptr. 对于vptr在对象中的位置,跟类的数据成员的布局一样,C++标准里面并没有做出任何的规定.但是对于特定的编译器,我们还是可以通过研究C++对象的内存布局来确定vtpr到底是放在哪里.

下面我们通过分析C++对象的内存布局,来寻找vptr的位置.在开始讨论之前我们先提供一个模板函数void PrintLayout(T const & obj),该函数用于打印obj所在内存的内容,下面是该函数的实现:

//PrintLayout.hxx #pragma once #include #include #include template void PrintLayout(T const & obj) { int * pObj = ReinterpretCast(&obj); for (int i =0; i

 

接下来通过代码来分析一下在C++里,在没有继承,单继承,多继承以及虚继承等情况下对象的内存布局,下面是示例代码,为了减少代码量,我们将类的所有数据成员设为public的,在这里我们用struct来代替class:

//main.cpp #include #include #include using namespace std; struct NoVirtualMemFunc { int Func1(int a,int b){ cout<<__FUNCTION__<<"/tm_iData="<

对于有虚表的函数,从上面的输出我们可以得到以下结论,

1.没有继承情况,vptr存放在对象的开始位置,以下是Base1的内存布局

vptr : 4294656

m_iData : 100

2.单继承的情况下,对象只有一个vptr,它存放在对象的开始位置,派生类子对象在父类子对象的最后面,以下是D1的内存布局

 

vptr : 4294740

B1:: m_iData : 100

B2:: m_iData :200

3.多继承情况下,对象会为每个有虚函数的父类子对象提供一个vptr,派生类子对象在所有父类子对象的最后面,所有父类子对象按照声明顺序排列,以下是D的内存布局

B1::vptr : 4294800

B1::m_iData : 100

B2::vptr : 4294776

B2::m_iData : 200

D::m_iData : 300

4. 虚拟继承情况下,虚父类子对象会放在派生类子对象之后,派生类子对象的第一个位置存放着一个vptr,虚拟子类子对象也会保存一个vptr,以下是VD1的内存布局 

VD1::vptr : 4294876

 Unknown : 4294888

VD1::m_iData : 200

B1::vptr : 4294864

B1::m_iData :100

5. 棱形继承的情况下,非虚基类子对象在派生类子对象前面,并按照声明顺序排列,虚基类子对象在派生类子对象后面

VD1::vptr :          4294944

VD1::Unknown : 4294968

VD1::m_iData :  200

VD2::vptr :     4   294932

VD2::Unknown : 4294952

VD2::m_iData :  300

VD::m_iData :  500

B1::vptr :         4294920

B1::m_iData :   100

接下来我们将通过代码来验证前面结论的准确性.下面的代码具有一定的局限性.在调试以下代码的时候,对虚拟继承遇到了以下几个让我迷惑的问题:

1.对于虚拟继承,函数指针的大小为12

2.用vtable里面的指针调用,this不能正确传进去

3.如果派生类的虚拟函数多于1个,则会crash

 

 

//main.cpp #include #include #include using namespace std; struct NoVirtualMemFunc { int Func1(int a,int b){ cout<<__FUNCTION__<<"/tm_iData="<::T_MemFuncT * vptr = ReinterpretCast::T_MemFuncT *>(GetVptr(obj)); CallMemFunc(2,vptr,obj); } //单继承 { cout<<"//单继承"<::T_MemFuncT * vptr = ReinterpretCast::T_MemFuncT *>(GetVptr(obj)); CallMemFunc(3,vptr,obj); } //多继承 { cout<<"//多继承"<::T_MemFuncT * vptr = ReinterpretCast::T_MemFuncT *>(GetVptr(obj)); CallMemFunc(3,vptr,objB1); Base2 &objB2 = obj; MemFuncT::T_MemFuncT * vptrB2 = ReinterpretCast::T_MemFuncT *>(GetVptr(objB2)); CallMemFunc(2,vptrB2,objB2); } #if 1 //虚拟继承 { cout<<"//虚拟继承"<::T_MemFuncT * vptr = ReinterpretCast::T_MemFuncT *>(GetVptr(obj)); CallMemFunc(1,vptr,obj); Base1 & objB1 =obj ; MemFuncT::T_MemFuncT * vptrB1 = ReinterpretCast::T_MemFuncT *>(GetVptr(objB1)); CallMemFunc(2,vptrB1,objB1); } //棱形继承 { cout<<"//棱形继承"<::T_MemFuncT * vptrB1 = ReinterpretCast::T_MemFuncT *>(GetVptr(objB1)); CallMemFunc(2,vptrB1,objB1); VD1 & objVD1 =obj; MemFuncT::T_MemFuncT * vptrVD1 = ReinterpretCast::T_MemFuncT *>(GetVptr(objVD1)); CallMemFunc(1,vptrVD1,objVD1); VD2 & objVD2 =obj; MemFuncT::T_MemFuncT * vptrVD2 = ReinterpretCast::T_MemFuncT *>(GetVptr(objVD2)); //CallMemFunc(1,vptrVD2,objVD2); } #endif return 0; } //输出 /* //没有继承,有虚函数的情况 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 //单继承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 D1::D1Func m_iData=200 a=500 b=600 //多继承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 D::DFunc m_iData=300 a=500 b=600 Base2::Base2Func1 m_iData=200 a=500 b=600 Base2::Base2Func2 m_iData=200 a=500 b=600 //虚拟继承 VD1::VD1Func m_iData=4294960 a=500 b=600 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 //棱形继承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 VD1::VD1Func m_iData=4295032 a=500 b=600 请按任意键继续. . . */

你可能感兴趣的:(C++和模板元编程,c++,struct,function,layout,编译器,c)