c++虚函数表探究(2018.9.18更新)

博客主要探讨了虚继承的sizeof问题,指出虚继承时内存模型会有很大差异。还分享了面试中常被问到的虚函数表问题,包括每个类实例都有虚函数表且地址在类内存地址最前,以及如何通过虚函数表访问虚函数甚至越权访问等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2018.9.18更新

关于虚继承的sizeof问题

#include <iostream>
#include <vector>
#include <string.h>
#include <stdio.h>
using namespace std;
class A
{
public:
	char a[4];
	virtual void aa() {}

	//const int Size;

};
class A2
{
public:
	char a2[4];
	virtual void aa2() {}
};
class B :virtual public A
{
	char j[3];
public:
	virtual void bb() {}
};
class C :public A
{
	char k[3];
public:
	virtual void cc() {}
};
class D :public virtual B
{
	char i[3];
public:
	virtual void cc() {}

};
class E :public virtual A, public virtual A2
{
	char q[4];
public:
	virtual void ee() {}
};
int main()
{
	A a;
	B b;
	C c;
	D d;
	E e;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(d) << endl;
	cout << sizeof(e) << endl;
}

本来以为自己很清楚这块了,结果发现完全不是这么回事。。。。。。

当发生虚继承时,内存模型(/d1reportSingleClassLayoutB ,/d1reportSingleClassLayoutE)会发生很大的差异。下图中vfptr是virtual funtion ptr,vbptr是virtual base ptr(虚基类指针)。

可以看到,B的内存中首先是自己的虚基类指针以及虚表指针,然后是char j[3],<alignment member>为占位符,接下来是虚基类A,A里面首先是vfptr,然后是char a[4],此时没有占位符。

1>class B	size(20):
1>	+---
1> 0	| {vfptr}
1> 4	| {vbptr}
1> 8	| j
1>  	| <alignment member> (size=1)
1>	+---
1>	+--- (virtual base A)
1>12	| {vfptr}
1>16	| a
1>	+---
1>
1>class E	size(28):
1>	+---
1> 0	| {vfptr}
1> 4	| {vbptr}
1> 8	| q
1>	+---
1>	+--- (virtual base A)
1>12	| {vfptr}
1>16	| a
1>	+---
1>	+--- (virtual base A2)
1>20	| {vfptr}
1>24	| a2
1>	+---
1>

虚基类E的内存模型如上图所示。

*****************************************************************************************************************************************

最近面试的时候被问了很多关于虚函数表的问题,大多数集中在如何通过虚函数表调用虚函数这种问题上,问得比较实在。

几个关键点:

1.每一个类对应的实例都会分配一个虚函数表,这个表的地址是分配在这个类所在内存地址的最前面:

#include<stdio.h>
class A
{
public:
	int n;
private:
	virtual void f0() { printf("the virtual function:f0()");}
        virtual void g0() { printf("the virtual function:g0()");}
};
class B: public A
{
        virtual void f0() { printf("the Derived function:f0()");}
};
typedef void(*Fun)(void);
int main()
{
	A a;
	B b;
	printf("%x",(long*)(&b));
	printf("\n%x\n",&(b.n));
	printf("%x",*(long*)(&b));
	Fun pfun = NULL;
        pfun = (Fun)(*((long*)(*(long*)(&b))+0));
	pfun();
	printf("\n%d %d",sizeof(int*),sizeof(long*));
}
743d50d0
743d50d8

这一点可以通过打印类B的地址和类B中第一个成员变量n的地址看出,他们相差了8,这正好是一个虚函数表指针的大小(64位系统)。

2.如何通过虚函数表访问类中的虚函数,甚至开始越权访问

这里涉及到的知识点主要是函数指针,https://www.cnblogs.com/hushpa/p/5707475.html网上很多的文章都是以前比较老的时候写的了,这里涉及到一个问题,如果使用的是64位系统,对&b强制转换时如果转成了(int*)是会报错的。

如果非要用,则必须开启g++ -m32选项,具体参考https://blog.youkuaiyun.com/dlfxjc2/article/details/78624554

但其实这里的原因我也不是非常清楚,这里待保留。

通过如下的代码,可以越权访问到本来不对子类开发的基类中private虚函数:

 pfun = (Fun)(*((int*)(*(int*)(&b))+1));
fff4993c
fff49940
5658feb8the virtual function:g0()

PS:此结果是在-m32 选项下编译的,故可以看到&n和虚函数表地址之间只差了4。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值