C++ virtual函数 实现机制——虚函数地址在虚表中的分布

C++ virtual函数 实现机制中,已经学习了虚函数和虚函数表的机制,但是还是很多细节没解决,这里就探讨一下夹心层的问题。

放例子——虚表跳转,利用虚函数地址强行执行:


#include<iostream>
#include<stdio.h>
using namespace std;

class B{
public:
        //virtual ~B(){cout <<"B destruction"<<endl;}//虚析构放在第一个,会直接导致取虚函数表第一个地址时运行崩溃
        virtual void func(){cout<<"B func."<<endl;}
        virtual void funcB(){cout <<"funcB."<<endl;}
        virtual void funcBB(){cout <<"funcBB."<<endl;}
//        virtual ~B(){cout <<"B destruction"<<endl;}
};

class DerivedB:public B
{
public:
        virtual ~DerivedB(){cout <<"DerivedB destruction"<<endl;}
        virtual void func0(){cout <<"DerivedB::func0."<<endl;}//如果没有基类B,此处应为虚函数表第一个地址指向,但是有了基类B,则完全不同
        virtual void func(){cout <<"DerivedB::func."<<endl;}//受基类B影响,仍然处于虚函数表头牌
        virtual void funcB(){cout <<"DerivedB::funcB."<<endl;}
        virtual void funcBB(){cout <<"DerivedB::funcBB."<<endl;}
        virtual void funcBBB(){cout <<"DerivedB::funcBBB."<<endl;}//按新老排序,在连续地址都找不到的实现
};

class DDB:public DerivedB
{
public:
       // virtual ~DDB(){cout <<"DDB destruction"<<endl;}
        virtual void func0(){cout <<"DDB::func0."<<endl;}
        virtual void func(){cout <<"DDB func."<<endl;}
        virtual void funcB(){cout <<"DDB::funcB."<<endl;}
        virtual void funcBB(){cout <<"DDB::funcBB."<<endl;}
        virtual void funcBBB(){cout <<"DDB::funcBBB."<<endl;}
        virtual void funcDDB(){cout<<"DDB::<span style="font-family: Arial, Helvetica, sans-serif;">funcDDB."<<endl;}</span>
};

typedef void(*pFun)(void);
int main()
{

        DerivedB b;
	DDB ddb;
        B *PtrB = &b;
	DerivedB* PtrDerivedB = &ddb;
	

	PtrB = &b;	
	PtrB->func();

	pFun pf = NULL;

	pf = (pFun)*((int*) *( (int*)&b ) );
	printf("*(int*)PtrB is %p\n",*(int*)PtrB);

	pf = (pFun)**((int**)PtrB);
	pf();

	pf = (pFun)*(*(int**)PtrB + 1);
	printf("pF point to %p\n",pf);
	pf();

	pf = (pFun)*(*(int**)PtrB + 2);
	printf("pF point to %p\n",pf);
	pf();

	cout << "---------------" << endl;

	pf = (pFun)**((int**)PtrDerivedB);
	printf("pF point to %p\n",pf);
	pf();

	pf = (pFun)*(*(int**)PtrDerivedB + 1);
	printf("pF point to %p\n",pf);
	pf();

	pf = (pFun)*(*(int**)PtrDerivedB + 2);
	printf("pF point to %p\n",pf);
	pf();

//	pf = (pFun)*(*(int**)PtrDerivedB + 3);//这是会崩溃的
//	printf("pF point to %p\n",pf);
//	pf();

	PtrDerivedB->funcBBB();

}


因为本来也不是合法途径,所以难免会遇到一些不如意的地方,下边就这些不如意的地方进行探讨!在开展探讨前,也不排除虚表的具体分布和编译器或平台有关,所以此处会崩溃(到底是调用到了虚析构,还是根本错误的地址,待会排查),不一定普遍适用!


这是一个双层继承的例子,一个DerivedB类对象,他的虚表指针在DerivedB类中的B类部分,指向Derived虚表。

一个DDB类对象,他的虚表指针却不在DDB类中的DerivedB类部分(显然,不然的话,一条继承线只有一个虚表指针又怎么够呢),而是在DDB类中的B类部分,指向DDB虚表。

也就是说,虚表指针都是在最最基类的部分,都有自己“重写”的虚表。


其实别扭的点就是,为什么DDB类中的DerivedB类部分不能按自己的实现重新以func0为起点,组织一个虚函数表,这样用DerivedB类指针指过来,才有基类的感觉。
但其实这个疑问也没道理。即使我是用DerivedB类指针去指向DDB类对象了,但是这并不代表DDB类对象真的可以无视B类的基础啊,B类指针也是可以指的,你不能因为DerivedB类隐藏B类中声明的虚函数,那个函数就失效了,DerivedB和DDB可能没重载,那样的话,还是需要B类的实现的。也不能因为DerivedB类中重新组织了一下函数的声明顺序,虚表就按照DerivedB来重新组织。原则上讲,隐藏了不代表没有,尽管重名了,B类那些func们的声明还是先于DerivedB的,你大爷,毕竟还是你大爷。顶多是被覆盖了,位置不变(比如 同样的虚表位置,直接把B::func地址替换为DerivedB::func()的地址)。

还是给个示意吧:
B类虚函数表:B::func()、B::funcB()、B::funcBB()
DerivedB类虚函数表: DerivedB::func()、 DerivedB::funcB()、 DerivedB::funcBB()、 DerivedB::funcBBB()、DerivedB::func0()
DDB类虚函数表: DDB::func()、 DDB::funcB()、 DDB::funcBB()、DDB::funcBBB()、DDB::func0()、 DDB::funcDDB()
紫色代表被重写红色代表新增!


那么现在问题就变成了,虚表中前三函数仍然是func()、funcB()、funcBB(),DerivedB类中,他们三个“后边”的funcBBB和“前边”的func0在虚表中的地址是什么?因为手动测试的话,虚表地址+3是不能执行的!!!




===============
这次帖子,把_vptr.B和各种_vptr.x的含义也学习一下吧,还不知道具体含义,至少知道特性,同类对象中_vptr.B是一样的,不同类对象中是不一样的。多重继承时,会有_vptr.A  _vptr.B等。

这个按理说就是第二层跳转了,严格上好像也不算跳转,算是个地址偏移吧?

(gdb) info locals
PtrB = 0xbffff5e0
PtrDerivedB = 0xbffff5dc
pf = 0x8048d90 <DDB::funcBB()>
b = {<B> = {_vptr.B = 0x8049188}, <No data fields>}
ddb = {<DerivedB> = {<B> = {_vptr.B = 0x8049148}, <No data fields>}, <No data fields>}

睡觉了,后续分析,to be continue
或许再回顾一下第一篇帖子,看看是不是分析过的







另外,虚析构在虚函数表中又是什么样的,毕竟不同于虚表中位置稳定、单纯改实现指向的虚函数func()和funcBB(),好歹名字还不一样呢(因为目前执行崩了,假设崩的是虚析构,修正一下,再尝试执行虚析构函数)













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值