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

vtpr的位置:
       为了支持多态,C++引入了vtpr和vtable这两个概念.对于每个有虚函数的类,C++都会为其生成一个vtable,并在类中添加一个隐含的数据成员vptr. 对于vptr在对象中的位置,跟类的数据成员的布局一样,C++标准里面并没有做出任何的规定.但是对于特定的编译器,我们还是可以通过研究C++对象的内存布局来确定vtpr到底是放在哪里.
      下面我们通过分析C++对象的内存布局,来寻找vptr的位置.在开始讨论之前我们先提供一个模板函数void PrintLayout(T const & obj),该函数用于打印obj所在内存的内容,下面是该函数的实现:


PrintLayout.hxx

#pragma once
#include <iostream>
#include <iomanip>
#include <ReinterpretCast.hxx>

template<typename T>
void PrintLayout(T const & obj)
{
	int * pObj = ReinterpretCast<int*>(&obj);
	for (int i =0; i<sizeof(obj)/sizeof(int);++i)
	{
		std::cout<<std::setw(10)<< pObj[i]<<std::endl;
	}
}


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



//main.cpp

#include <iostream>
#include <PrintLayout.hxx>
#include <typeinfo>
using namespace std;

struct NoVirtualMemFunc
{
	int  Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int  Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct Base1
{
	virtual int  Base1Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	virtual int  Base1Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct Base2
{
	virtual int  Base2Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	virtual int  Base2Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};

struct D1:public Base1
{
	virtual int  D1Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct D:public Base1,public Base2
{
	virtual int  DFunc(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};

struct VD1:public virtual Base1
{
	virtual int  VD1Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct VD2:public virtual Base1
{
	virtual int  D2Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct VD:public VD1,public VD2
{
	int m_iData;
};



template<typename T>
void PRINT_LAYOUT(T const & obj)
{
	cout<<"The layout of "<<typeid(obj).name()<<"----------------"<<endl;
	PrintLayout(obj);
	cout<<endl;
}
int main(int argc, char* argv[])
{
	//没有继承,没有虚函数的情况
	{
		NoVirtualMemFunc obj;
		obj.m_iData = 100;
		PRINT_LAYOUT(obj);
	}
	

	//没有继承,有虚函数的情况
	{
		Base1 obj;
		obj.m_iData = 100;
		PRINT_LAYOUT(obj);
	}
	

	//单继承
	{
		D1 obj;
		obj.Base1::m_iData = 100;
		obj.m_iData = 200;
		PRINT_LAYOUT(obj);
	}
	

	//多继承
	{
		D obj;
		obj.Base1::m_iData  = 100;
		obj.Base2::m_iData  = 200;
		obj.m_iData = 300;
		PRINT_LAYOUT(obj);
	}
	

	//虚拟继承
	{
		VD1 obj;
		obj.Base1::m_iData = 100;
		obj.m_iData = 200;
		PRINT_LAYOUT(obj);
	}
	

	//棱形继承
	{
		VD obj;
		obj.Base1::m_iData = 100;
		obj.VD1::m_iData   = 200;
		obj.VD2::m_iData   = 300;
		obj.m_iData       = 500;
		PRINT_LAYOUT(obj);

	}
	return 0;
}

//输出
/*
The layout of struct NoVirtualMemFunc----------------
      100

The layout of struct Base1----------------
   4294656
       100

The layout of struct D1----------------
   4294740
       100
       200

The layout of struct D----------------
   4294800
       100
   4294776
       200
       300

The layout of struct VD1----------------
   4294876
   4294888
       200
   4294864
       100

The layout of struct VD----------------
   4294944
   4294968
       200
   4294932
   4294952
       300
       500
   4294920
       100

请按任意键继续. . .



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

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 <iostream>
#include <GetVptr.hxx>
#include <typeinfo>
using namespace std;

struct NoVirtualMemFunc
{
	int  Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int  Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct Base1
{
	virtual int  Base1Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	virtual int  Base1Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct Base2
{
	virtual int  Base2Func1(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	virtual int  Base2Func2(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};

struct D1:public Base1
{
	virtual int  D1Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct D:public Base1,public Base2
{
	virtual int  DFunc(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};

struct VD1:public virtual Base1
{
	virtual int  VD1Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct VD2:public virtual Base1
{
	virtual int  D2Func(int a,int b){
		cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;
		return 0;
	}
	int m_iData;
};
struct VD:public VD1,public VD2
{
	int m_iData;
};

template<class T>
struct MemFuncT
{
	typedef int (T::* T_MemFuncT)(int,int);
	typedef int (T::* T_MemDataT);
};
template<class C>
void CallMemFunc(int iFuncNum,int (C::**vptr)(int,int),C& obj,int a =500,int b =600)
{
	for (int i =0;i<iFuncNum;++i)
	{
		//cout<<ReinterpretCast<void*>(vptr[i])<<"   ";
		(obj.*vptr[i])(a,b);
	}
	cout<<endl;
}
int main(int argc, char* argv[])
{


	//没有继承,有虚函数的情况
	{
		cout<<"//没有继承,有虚函数的情况"<<endl;
		Base1 obj;
		obj.m_iData = 100;
		MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));
		CallMemFunc(2,vptr,obj);
	}
	

	//单继承
	{
		cout<<"//单继承"<<endl;
		D1 obj;
		obj.Base1::m_iData = 100;
		obj.m_iData = 200;
		MemFuncT<D1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<D1>::T_MemFuncT *>(GetVptr(obj));
		CallMemFunc(3,vptr,obj);
	}
	

	//多继承
	{
		cout<<"//多继承"<<endl;
		D obj;
		obj.Base1::m_iData  = 100;
		obj.Base2::m_iData  = 200;
		obj.m_iData = 300;

		Base1 &objB1 = obj;
		MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));
		CallMemFunc(3,vptr,objB1);

		Base2 &objB2 = obj;
		MemFuncT<Base2>::T_MemFuncT * vptrB2 = ReinterpretCast<MemFuncT<Base2>::T_MemFuncT *>(GetVptr(objB2));
		CallMemFunc(2,vptrB2,objB2);
	}
	
#if 1
	//虚拟继承
	{
		cout<<"//虚拟继承"<<endl;
		VD1 obj;
		obj.Base1::m_iData = 100;
		obj.m_iData = 200;
		MemFuncT<VD1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(obj));
		CallMemFunc(1,vptr,obj);

		Base1 & objB1 =obj ;
		MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));
		CallMemFunc(2,vptrB1,objB1);
	}
	

	//棱形继承
	{
		cout<<"//棱形继承"<<endl;
		VD obj;
		obj.Base1::m_iData = 100;
		obj.VD1::m_iData   = 200;
		obj.VD2::m_iData   = 300;
		obj.m_iData       = 500;

		Base1 & objB1 = obj;
		MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));
		CallMemFunc(2,vptrB1,objB1);

		VD1   & objVD1 =obj;
		MemFuncT<VD1>::T_MemFuncT * vptrVD1 = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(objVD1));
		CallMemFunc(1,vptrVD1,objVD1);

		VD2   & objVD2 =obj;
		MemFuncT<VD2>::T_MemFuncT * vptrVD2 = ReinterpretCast<MemFuncT<VD2>::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

请按任意键继续. . .
*/




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值