题目内容

该问题考察对虚函数表的理解,虚函数表就是是一个类存储虚函数地址的指针列表,每一个声明的虚函数(不论是新建还是重写)都会写入虚函数表的对应位置。

虚函数表的地址在vc编译器下存储在每个对象内存空间的最前面,通过解引用可以访问虚函数表的首地址,接下来我们可以通过遍历等方式访问里面存的函数地址。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>

using namespace std;
class A
{
public:
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f"<<endl;
}

};
class B :public A
{
public:
virtual void g()
{
cout << "B::g" << endl;
}
virtual void h()
{
cout << "B::h" << endl;
}
};

typedef void(*Fun_t)(void);

int main()
{
B b;
Fun_t pfun;
for (int i = 0; i < 3; ++i)
{
pfun = (Fun_t)*((*(int***)(&b)) + i);
pfun();
}
return 0;
}

它的输出结果为:

1
2
3
B::g
A::f
B::h

关键代码解析

我们着重拆解这行代码

1
pfun = (Fun_t)*((*(int***)(&b)) + i);
  1. 获取虚表地址: 在vc编译器下虚表地址就是对象地址的第一段指针地址,所以使用&b获取虚表地址–(&b)
  2. 获取虚表首地址: 因为(&b)是B*类型的指针,所以我们要把它转换为列表类型的指针再解引用,所以添加(int***)把它强转为int*指针列表的地址指针,然后解引用获取虚函数列表首地址–(*(int***)(&b))
  3. 使用偏移量:我们要根据不同的偏移量获取不同的函数地址,所以需要偏移指针,同时为保证指针一定偏移了一个指针的长度,我们先前就把它转换成了int*指针列表的指针,因此加上常数时,移动的是一个指针长度的地址–((*(int***)(&b)) + i)
  4. 获取函数地址: 此时指针指向了列表的元素的地址,我们需要解引用才能访问元素,及函数地址,所以要先解引用–*((*(int***)(&b)) + i)
  5. 存储函数指针:上面的方法我们已经获取了函数指针的值,但是它是int*类型的指针,所以还要强制转换一下–(Fun_t)*((*(int***)(&b)) + i)
  6. 执行函数:通过pfun()调用pfun指针指向的函数