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>}
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(),好歹名字还不一样呢(因为目前执行崩了,假设崩的是虚析构,修正一下,再尝试执行虚析构函数)