在深入研究虚函数表之前,我们先思考几个问题:
1、虚函数表存储在什么地方
2、虚函数表中的内容是什么时候确定的
3、虚函数表的指针什么时候赋值
在windows系统下,虚函数表存储在只读数据段(.rdata),也就是说虚函数表在编译阶段就已经形成了,虚函数表指针是在构造函数中赋值的。
相关解释参考 C++虚函数表详细解释及实例分析
但是我还是想更加深入的探索一下虚函数表的实现机制:
测试代码如下:
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(){}
Base1(int i){ _i = i; }
~Base1(){}
virtual int get(){ return _i; }
virtual void set(){}
private:
int _i;
};
class Base2
{
public:
Base2(){}
Base2(int i){ _i = i; }
~Base2(){}
virtual int get(){ return _i; }
virtual int get2(){ return _i; }
virtual void set2(){}
private:
int _i;
};
class Sec:public Base1,public Base2
{
public:
Sec(){}
Sec(int i, int j) :Base1(i),Base2(j), _j(j){}
~Sec(){}
int get(){ return _j; }
int get2(){ return _j; }
//void set(){}
private:
int _j;
};
int main()
{
typedef int(*Fun)();
Base1 b(1);
Base2 b2(2);
Sec s(3, 4);
Base1 *p = &s;
int k = p->get();
Base2 *q = &s;
int m = q->get();
int n = (unsigned)&s;
return 0;
}
在Debug下发现 n 与p的值是相等的,但是p与q的地址是不同的。而且有意思的是 在Sec的虚函数表中,Base1的get函数地址与Base2的get函数地址其实是不一样的,但是在运行过程中,都调用了Sec的get函数。图示如下:
到底是为什么呢?
于是便想通过ida的强大的反汇编功能就可以了解一二。
以上代码是main函数下的一部分片段,红线部分是调用Sec类的构造函数,跟进去看一下
可以看到在Sec类的构造函数中,分别执行Base1和Base2的构造函数,然后再将Base1和Base2的虚函数表的地址赋值给变量s。其实,在Base1和Base2构造函数中也是先执行构造函数然后再将虚函数表的指针赋值,只不过被后来的虚函数表指针给覆盖掉了。然后再看看虚函数表的情况:
带着蓝字中的问题,再看一下虚函数表的具体情况:
以下是Base2的情况:
可以看到j_?get@Sec@@UAEHXZ就是Base1虚函数表中get函数的地址。说明,虽然在Sec的虚函数表中,Base1的get函数地址与Base2的get函数地址不一样,但是最终指向的函数地址是一致的。