尝试修改虚函数表
本期纯整活儿好吧!!!!
初衷
有一天我突然开始好奇虚函数表是否真的存在,于是我开始想是否能够从C++中查看或者调用虚函数表中的内容。,于是有了下面的操作。
操作过程
起初我并没有思路,但是我知道,每一个类对应一个虚函数表,因此首先我需要一个虚函数,因此我随便写了一个基类:
class Base {
public:void output() {cout << "Class Base" << endl; };virtual void say() {cout << "Class Base" << endl;}
};
然后写一个子类,去 override
一下他的这个函数:
class A : public Base {
public:void output() {cout << "Class A" << endl;}void say() override {cout << "Class A" << endl;}int x;
};
然后按照同样的方式再创建一个 B 类:
class B : public Base {
public:void output() {cout << "Class B" << endl;}void say() override {cout << "Class B" << endl;}
};
这样以来,应该会有三个虚函数表,分别是:
- Base基类对应的虚函数表
- A类对应的虚函数表
- B类对应的虚函数表
然后如何调用他们呢?我想了好久,想出这样的一个方法:
int main() {A a;B b;cout << "A's virtual table address : " << ((void **)(&a))[0] << endl;cout << "A's virtual table address : " << ((void **)(&b))[0] << endl;return 0;
}
根据理论来说,C++中的虚函数表应该在类内空间的第一个位置,占八个字节,是一个指向函数表的指针,那么我们就应该这样做:
((void **)(&b))[0];
这会返回一个虚函数表的地址。
这句话是什么意思呢?首先我们要清楚,对象的空间分配与结构体是一样的,而根据理论来看,虚函数表的指针会被编译器自动添加在对象空间的初始位置,也就是说,对象所在的空间的第一个单元存储的是虚函数表的地址。
如何获得这个首地址呢?首先我们要像取数组首地址一样,用取地址符号获得对象的首地址。然后将其强制转换为 (void **)
类型,这相当于让电脑将这个对象的空间看作一个数组,这个数组中存放的全部都是指向 void *
类型的数据的地址。
void *
类型是函数指针类型,我们不用管,最后在末尾添加[0]
就相当于得到了虚函数表的地址。
尝试输出一下:
嗯,看起来没啥问题,但是如何证明他是个虚函数表的地址呢?
我能否将一个类中的修改到另一个虚函数表中?然后让这个对象执行的时候出现另外一个类的动作?
于是我开始了下面的尝试:
int main() {A a;B b;cout << "Class A virtual table address : " << ((void **)(&a))[0] << endl;cout << "Class B virtual table address : " << ((void **)(&b))[0] << endl;((void **)(&a))[0] = ((void **)(&b))[0]; // 把b对应的类的虚函数表覆盖到a上a.say(); // 如果虚函数表被覆盖了的话,那么就会出现a执行了b的say方法的状况b.say();return 0;
}
然而结果是这样的:
发现结果并没有被改变,这是怎么回事?我百思不得其解,多方询问过之后了解到是gcc编译器把我的虚函数的调用过程给优化掉了,无奈我只能使用指针和引用来赋值:
int main() {A a;B b;Base *ap = &a, *bp = &b;cout << "Class A virtual table address : " << ((void **)(&a))[0] << endl;cout << "Class B virtual table address : " << ((void **)(&b))[0] << endl;((void **)(&a))[0] = ((void **)(&b))[0]; // 把b对应的类的虚函数表覆盖到a上ap->say(); // 如果虚函数表被覆盖了的话,那么就会出现a执行了b的say方法的状况bp->say();return 0;
}
执行成功啦!!!
其实后面我还做了很多好玩的操作,这里先不放出来,写的有点累,下次再凑出一篇来!
:wq 拜拜~~