C++对象模型之虚基类表和虚函数表的布局(一)

C++对象模型之虚基类表和虚函数表的布局(一)

注:本文的c++对象结构模型基于vs编译器的win32环境,只有microsoft编译器处理虚基类时使用虚基类表的形式,而其他编译器大多采用在虚函数表中放置虚基类的偏移量的方式。
链接: C++对象模型之虚基类表和虚函数表的布局(二).
链接: C++对象模型之虚基类表和虚函数表的布局(三).

一、虚函数表

当一个类包含一个或多个虚函数时,编译器会在他的对象中创建一个虚函数指针,这个虚函数指针指向虚函数表,虚函数表里记录者这些虚函数的地址。很多人好奇虚函数指针、虚函数表、虚函数分别处于内存的什么位置?
答案只有看看汇编部分才能知晓,据笔者的观察,虚函数表位于全局数据区,可供各个对象调用;而虚函数位于代码区
而当一个子类继承了多个父类时,子类的虚函数表就会变得复杂,我们通过代码实验的方式,来探究c++对象模型中虚函数表的布局。

class Base1 
{
public:
    int m_base1;
    static int n_base1;
public: 
    virtual void x() { cout << "Base1::x()" << endl; }
    virtual void y() { cout << "Base1::y()" << endl; }
    virtual void z() { cout << "Base1::z()" << endl; }
};
int Base1::n_base1 = 1; //静态成员变量在类外初始化

class Base2 
{
public:
	int m_base2;
public:   
    virtual void a() { cout << "Base2::a()" << endl; }
    virtual void b() { cout << "Base2::b()" << endl; }
};

class Derived : public Base1, public Base2
{
public:
	int m_derived;
public:    
    virtual void y() { cout << "Derived::y()" << endl; }
    virtual void m() { cout << "Derived::m()" << endl; }
    virtual void n() { cout << "Derived::n()" << endl; }
};

测试代码:

(1)首先看看这几个类对象占内存的大小
int main()
{
	cout << sizeof(Base1) << endl;   //8byte
	cout << sizeof(Base2) << endl;   //8byte
	cout << sizeof(Derived) << endl; //20byte
	return 0;
}

分析:占用类对象空间的成员是非静态成员变量、虚函数表指针。静态成员变量和成员函数均不占用类对象空间,他们跟着类走。

  • 对于Base1,4byte的虚函数表指针vftpr1 + 4byte的m_base1 = 8byte
  • 对于Base2,4byte的虚函数表指针vfptr2 + 4byte的m_base2 = 8byte
  • 对于Derived,4byte的虚函数表指针vftpr1 + 4byte的m_base1 + 4byte的虚函数表指针vbptr2 + 4byte的m_base2 + 4byte的m_derived = 20byte
(2)其次看看类对象的空间分布
int main()
{
	typedef void(*Func)(void);

	Base1 *base1 = new Base1;
	
	long* ptrB = reinterpret_cast<long*>(base1);
	printf("ptrB is 0x:%p\n", ptrB);
	printf("m_base1 is %d\n", *(ptrB + 1));

	long* vptrB = reinterpret_cast<long*>(*ptrB); //虚函数表首地址
	for (int i = 0; i < 4; i++) //打印虚函数表内容
	{
		printf("vptrD[%d] = 0x:%p\n", i, vptrD[i]);
	}

	cout << "=======================================================" << endl;
	//观察虚函数
	Func ptr0 = (Func)vptrB[0];
	Func ptr1 = (Func)vptrB[1];
	Func ptr2 = (Func)vptrB[2];
	Func ptr3 = (Func)vptrB[3];

	ptr0();
	ptr1();
	ptr2();
	ptr3();
	delete base1; //养成好习惯,防止内存泄漏
	base1 = NULL; //养成好习惯,防止出现野指针
	return 0;
}

在这里插入图片描述
以上结果足以说明,如果类中有虚函数,那么该类的对像空间布局为:首先放置虚函数表指针,随后放置成员变量。(当然堆区空间是由低地址向高地址生长的)我们随后观察子类来验证并扩充这一点。下图正式子类对象空间布局图。
在这里插入图片描述

(3)再看看虚函数表的布局

我们直接从最复杂的子类入手:

int main()
{
	typedef void (*Func)(void);
	Derived *derived = new Derived;
	

	long* ptrD = reinterpret_cast<long*>(derived);
	//printf("ptrD is 0x:%p\n", ptrD);
	//printf("m_base1 is %d\n", *(ptrD + 1));

	long* vptrD = reinterpret_cast<long*>(*ptrD); //虚函数表首地址
	for (int i = 0; i < 10; i++) //打印虚函数表内容
	{
		printf("vptrD[%d] = 0x:%p\n", i, vptrD[i]);
	}

	cout << "===========================================================" << endl;
	//观察虚函数
	Func ptr0 = (Func)vptrD[0];
	Func ptr1 = (Func)vptrD[1];
	Func ptr2 = (Func)vptrD[2];
	Func ptr3 = (Func)vptrD[3];
	Func ptr4 = (Func)vptrD[4];
	
	Func ptr7 = (Func)vptrD[7];
	Func ptr8 = (Func)vptrD[8];


	ptr0();
	ptr1();
	ptr2();
	ptr3();
	ptr4();
	ptr7();
	ptr8();

	cout << "===========================================================" << endl;
	cout << "===========================================================" << endl;
	long* vptrD2 = reinterpret_cast<long*>(*(ptrD+2)); //虚函数表首地址

	for (int i = 0; i < 3; i++) //打印虚函数表内容
	{
		printf("vptrD2[%d] = 0x:%p\n", i, vptrD2[i]);
	}

	cout << "===========================================================" << endl;
	//观察虚函数
	Func ptr00 = (Func)vptrD2[0];
	Func ptr11 = (Func)vptrD2[1];


	ptr00();
	ptr11();



	delete derived;
}

在这里插入图片描述
由此可以看出:

  • 1.一个带有虚函数的类会产生一个虚函数表,继承自父类的虚函数表指针指向该虚函数表的不同位置。
  • 2.子类自己的虚函数归在第一个父类的虚函数指针指向区域中。
  • 3.虚函数表区域结束以0x00000000为标志。
  • 4.子函数重写父类的虚函数会自动替换虚函数表中的虚函数。

Derived类产生的虚函数表:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值