继承关系作用下虚函数的手工调用

通过虚函数表指针调用虚函数

创建一个父类,一个子类,尝试着通过虚函数表指针调用虚函数

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

class Derive : public Base
{
public:
	void g() { cout << "Derive::g()" << endl; }
};

int main()
{
	cout << sizeof(Base) << endl;	// 4
	cout << sizeof(Derive) << endl;	// 4
	cout << sizeof(long) << endl;
	
	Derive* d = new Derive(); // 派生类指针,其实用基类指针指向派生类也一样 Base *d = new Derive()
	long* pvptr = (long*)d;
	long* vptr = (long*)(*pvptr);

	for (int i = 0; i <= 4; i++) {
		printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
	}
	typedef void(*Func)(void); // 定义了一个函数指针,typedef就代表Func是一个函数指针类型,可以将它当作类型使用
	Func f = (Func)vptr[0];
	Func g = (Func)vptr[1];
	Func h = (Func)vptr[2];
	Func i = (Func)vptr[3];
	Func j = (Func)vptr[4];
	f();
	g();
	h();
	//i();
	//j();

	return 0;
}

分析代码

着重分析一下下面的代码

Derive* d = new Derive(); // 派生类指针,其实用基类指针指向派生类也一样 Base *d = new Derive()
long* pvptr = (long*)d;
long* vptr = (long*)(*pvptr);
  • Derive *d = new Derive();
  • long *pvptr = (long *)d;
  • long *vptr = (long*)(*pvptr);
  • 未强制类型转换前,d指向的是创建出的Derive的整个对象大小的内存空间的首地址
  • 我们实质只关注这个类对象的内存空间中的存虚函数表首地址内存的那部分中的空间,也就是long类型大小的空间(一个指针变量的大小也就占long这么长),此时pvptr就是单纯的指向Derive对象的虚函数表首地址的那部分内存
  • long *vptr = (long*)(*pvptr); *pvptr是虚函数表首地址的值,将这个值强制类型转换为一个long类型地址值,即vptr是一个指向long类型的指针变量,它的值就是虚函数表首地址。
    ————————————————
    版权声明:本文为CSDN博主「NGC_2070」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/baidu_41388533/article/details/109497379

自己的理解

  • 我们想要得到虚函数表里的函数地址,但是最开始获得的是对象的开始地址,即d,而我们要找到虚函数的地址就需要先找到虚函数表指针,而虚函数表指针在对象的开头并且指针的长度为long,所以我们可以通过强制类型转换限制指向的内存,即long *pvptr = (long *)d,此时得到的pvptr就是虚函数表指针的那段地址。
  • 但是仅仅这样还是无法直接遍历得到虚函数,比如使用for(; ; )每次增加1,对于pvptr指针来说是在对象的内存中增加一个long的大小,我们得到是虚函数的地址吗,显然不是。所以我们先要得到虚函数表指针所指的值(解引用),然后再让新的指针指向它,即long *vptr = (long*)(*pvptr)*pvptr即虚函数表的地址,对地址强制类型转换是需要加*的,然后再让一个新的指针指向此处被转换的地址,对这个vptr指针进行遍历,其增加1,就是在虚函数表地址开头那增加一个long,此时获得的就是不同的虚函数了
  • 第二点可以换句话说,我们需要在虚函数表的那块内存上循环遍历得到,而pvptr是在虚函数表指针的那块内存上循环遍历,所以需要解引用并且强制转换
img

使用定义的函数指针调用虚函数

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

class Derive : public Base
{
public:
	void g() { cout << "Derive::g()" << endl; }
};

int main()
{
	cout << sizeof(Base) << endl;	// 4
	cout << sizeof(Derive) << endl;	// 4
	cout << sizeof(long) << endl;
	
	Derive* d = new Derive(); // 派生类指针,其实用基类指针指向派生类也一样 Base *d = new Derive()
	long* pvptr = (long*)d;
	long* vptr = (long*)(*pvptr);

	for (int i = 0; i <= 4; i++) {
		printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
	}
	typedef void(*Func)(void); // 定义了一个函数指针,typedef就代表Func是一个函数指针类型,可以将它当作类型使用
	Func f = (Func)vptr[0];
	Func g = (Func)vptr[1];
	Func h = (Func)vptr[2];
	Func i = (Func)vptr[3];
	Func j = (Func)vptr[4];
	f();	// Base::f
	g();	// Derive::g 子类覆盖父类的虚函数
	h();	// Base::h
	//i();
	//j();

	return 0;
}
4
4
4
vptr[0] = 0x:00B71104
vptr[1] = 0x:00B71078
vptr[2] = 0x:00B714D3
vptr[3] = 0x:00000000
vptr[4] = 0x:69726544
Base::f()
Derive::g()
Base::h()

我们发现前面vptr[]三个的地址在一个范围内,后面两个距离有些远

我们使用自定义的函数指针,调用虚函数表里面的函数,从上到下依次是

Base::f()
Derive::g()
Base::h()

这符合我们所想的空间布局,Derive继承父类,所以虚函数表里有父类的虚函数,然后因为Derive自定义了Derive::g(),所以覆盖了父类的Base::g()我们得到的是子类的函数。

再增加父类对象虚函数表的打印

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

class Derive : public Base
{
public:
	void g() { cout << "Derive::g()" << endl; }
};

int main()
{
	cout << sizeof(Base) << endl;	// 4
	cout << sizeof(Derive) << endl;	// 4
	cout << sizeof(long) << endl;
	
	cout << "对子类" << endl;
	Derive* d = new Derive(); // 派生类指针,其实用基类指针指向派生类也一样 Base *d = new Derive()
	long* pvptr = (long*)d;
	long* vptr = (long*)(*pvptr);

	for (int i = 0; i <= 4; i++) {
		printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
	}
	typedef void(*Func)(void); // 定义了一个函数指针,typedef就代表Func是一个函数指针类型,可以将它当作类型使用
	Func f = (Func)vptr[0];
	Func g = (Func)vptr[1];
	Func h = (Func)vptr[2];
	Func i = (Func)vptr[3];
	Func j = (Func)vptr[4];
	f();
	g();
	h();
	//i();
	//j();

	
	Base* dpar = new Base();
	long* pvptrpar = (long*)dpar;
	long* vptrpar = (long*)(*pvptrpar);

	cout << "对父类" << endl;
	for (int i = 0; i <= 4; i++) {
		printf("vptr_Base[%d] = 0x:%p\n", i, vptrpar[i]);
	}
	Func fpar = (Func)vptrpar[0];
	Func gpar = (Func)vptrpar[1];
	Func hpar = (Func)vptrpar[2];
	Func ipar = (Func)vptrpar[3];
	Func jpar = (Func)vptrpar[4];
	fpar();
	gpar();
	hpar();


	return 0;
}
4
4
4
对子类
vptr[0] = 0x:00611104
vptr[1] = 0x:00611078
vptr[2] = 0x:006114D3
vptr[3] = 0x:00000000
vptr[4] = 0x:69726544
Base::f()
Derive::g()
Base::h()
对父类
vptr_Base[0] = 0x:00611104
vptr_Base[1] = 0x:00611226
vptr_Base[2] = 0x:006114D3
vptr_Base[3] = 0x:00000000
vptr_Base[4] = 0x:65736142
Base::f()
Base::g()
Base::h()

总结

  • 父类对象调用的虚函数都是父类的
  • 父类子类虚函数表所指向的虚函数首地址:[0] [2]都相同,都代表Base::f() Base::h()的首地址,而[1]不同,代表子类Derive::g()的首地址和父类Base::g()的首地址

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值