C++ 指针强取虚函数地址推导

 关于怎样输出类的虚函数地址,网上已经有很多了。

例如下面的例子

//...
#pragma pack(4)
//...
class A {
public:
	double base_data;
	A() { 
		base_data = 233; 
	}
	void func() { cout << "fvck you" << endl; }
	virtual void func1() { cout << "base_func1" << endl; }
	virtual void func2() { cout << "base_func2" << endl; }
	virtual void func3() { cout << "base_func3" << endl; }
};
int main()
{
	Base base;
	A a;
	cout << sizeof(A) << endl;
	typedef void(*Fun)(void);
	void (*pFunc)();
	//cout << (int*)&a<<endl;
	//cout << (int*)&(a.base_data) << endl;
	auto kk = (int*)*((int*)&a)+2;
    //kk是int*
	pFunc = (Fun)(*kk);
	pFunc();
//...

在32位平台(vs2017,x86),会输出:

12
base_func3

关键在于得到pFunc整一个式子:

pFunc=(Fun)*((int*)*((int*)&a)+2)

看起来着实有点恐怖,对于其解释我感觉有些人说得并不够细,我在此推导一下。

(注意:以下数字大小都依赖vs2017,x86)

首先了解原理,你会发现上面代码sizeof(A)是12,这12个字节都是什么呢?(注意我使用pack(4)避免内存对齐带来的困扰)

12=4+8。后面的8是double base_data的大小,那前面的4呢?

对于任何有虚函数的类,前4个字节都是用作存储虚函数表的地址。

虚函数表是一个数组vtable,那么A类中这4个字节就用来存储数组头的地址vptr=vtable,vtable[2]=*(vtable+2)=*(vptr+2)。

图中每一个小矩形代表一个字节。不要忘了32位下一个指针大小为4字节(即4*8=32bit)。

PS:

对于double arr[2]={3.2,4.7};

cout<<*(arr+1);就能得到4.7

本质是对地址arr+1*sizeof(double),进行double型取值(也就是往后取8字节数据)。

PS:

这也是32位程序内存不能超过4GB的原因,因为

4gb=2^{2}*2^{10}*2^{10}*2^{10}=2^{32}b

在32位下,一个4字节的指针只能表示2^{32}种不同的状态,也就是2^{32}个不同的字节位置。寻址的最小单元为字节。

 

所以,我们明白了两件事情:

1.取&a开始的前4字节,就能得到指针vptr。

2.对vptr进行类似数组的取值,就能得到一个虚函数的指针。

那怎么取4字节呢?用int将这4字节的数据保存

1.先取得vptr

显然,无法将a直接转为int 或指针类型,就能得到其数据的前4字节。

那么只能绕一步,先取得a的地址,并强转为指针类型,这里使用int*,因为后面要取4字节存到int。

int* a_intp=(int*)&a

现在,我要取值,即从首地址开始的4个字节数据,存到一个int里。

int vptr_int=*(a_intp)
            =*((int*)&a)

vptr是vtable数组的首地址,也就是一个指针,但它现在类型为int。我们得把他转化为指针,再取值,才能得到vtable。

化为的指针类型还是为int*,原因同上:

int* vptr=(int*)(vptr_int)
         =(int*)*((int*)&a)

通过类似数组的取值方式,取得第3个元素,也就是func3的函数指针。但由于vptr是int*类型,取值只能取得int类型,所以我们得到的函数指针被存进了一个int:

int func3_int=*(vptr+2)
             =*((int*)*((int*)&a)+2)

 

最后,对func3_int的数据强转为可调用的函数指针,以便调用:

Fun func3=(Fun)(fun3_int)
         =(Fun)*((int*)*((int*)&a)+2)

这样,指针强取虚函数地址的推导就完成了。

PS:

VS2017打断点的时候可以查看a的_vptr的值,可以对照一下与自己算的值是不是一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值