C++ -- 探索虚函数表
一.虚函数表
1.虚函数:类的成员函数前面加上virtual关键字。
2.虚函数表:用来存放虚函数地址的一张表。
3.虚函数表就像一张地图,指明了要调用的虚函数。
二.虚函数表的小知识
1.一个类中只要含有虚函数,则一定会生成一张虚表。
(1)一个类中含有虚函数,其对象监视窗口如下,可以看到A类的对象a1中含有一个虚表指针(_vfptr),这个虚表指针指向了一个虚表,虚表里面含有两个元素,func1和func2函数的地址:
(2)一个类中不含有虚函数,则其对象的监视窗口如下(可以看到A类的对象a就只含有成员变量,没有虚表指针):
2.对象里面并不是存放整个虚表的,对象里面只存放了一个指向虚表的指针。(由上面图可以看出,对象里面只存放一个虚表指针_vfptr)
- 为什么不存放整个虚表?
如果一个对象里面存放整个虚表,不仅会使该对象变得很大,而且会使得与其同类的所有对象里面都存放一个虚表,但是每个对象的虚表都是相同的,为了节省空间还有提高效率,应该让同类的所有对象指向同一个虚表,这样只需要每个对象里面存放一个指向虚表的指针就可以了。
3.同类型的所有对象共用一块虚表。
例如:在静态区定义一个A的对象a3,在栈上定义两个A的对象a1和a2,通过监视窗口可以看到A的所有对象a1,a2,a3里面的虚表指针的内容都相同,都是0x003acc74,表明他们的虚表的地址都相同,所以他们共用同一块虚表。
三.通过自己编写的程序打印虚表
1.由上面的图可以知道,虚表指针放在对象的头上(在32位的平台上是头上4个字节,在64位的平台上是头上8个字节)。
2.下面以32位平台为例:
3.若给定一个对象,我们可以先取对象的前面4个字节,然后解引用取出头上4个字节的内容(即虚表的地址),让这个虚表以4字节为单位(因为虚表里面存放的是虚函数的地址,在32位下是4个字节),依次往后遍历,直到遇到0就结束(虚表是以0作为结束标志)。
4.我们可以将虚表里面的内容看做int,最后只要以%p的形式打印虚表的内容就可。
5.将虚表的内容看作int,则虚表就相当于一个存放int的数组,即int *table.
6.如果给定了虚表int *table,则可以按照下面的方式打印:
void PrintVFtable(int *table)
{
for (size_t i = 0; table[i] != 0; ++i)
{
printf("[%d] %p\n", i,table[i]);
}
cout << endl;
}
7.调用打印虚表传的参数如下:
int main()
{
A a1;
PrintVFtable((int*)(*((int*)&a1)));
system("pause");
return 0;
}
8.运行结果如下:
9.为了方便观察,我们可以将对应地址的虚函数也打印出来:
(1)定义一个函数指针,因为A类里面所有的虚函数返回值都是void,而且无参数,所以函数指针可以定义为:
typedef void(*VFUNC)();
(2)打印虚表函数的代码进行如下修改:
void PrintVFtable(int *table)
{
for (size_t i = 0; table[i] != 0; ++i)
{
printf("[%d] %p ->", i,table[i]);
VFUNC f = (VFUNC)table[i];
f();
}
cout << endl;
}
(3)运行结果如下:
四.在64位平台下打印虚表
1.在64位下如果采用上面的方式打印虚表会出现错误。因为在64位平台下指针占8个字节,而在32位平台下,指针占四个字节。
2.所以在64位平台下:应该取对象的头上8个字节,解引用取出里面的内容,然后再按照8字节为单位遍历虚表即可。
3.我们直到long long占8个字节,因此可以将代码进行如下修改:(这个只能在64位的平台运行)
void PrintVFtable(long long *table)
{
for (size_t i = 0; table[i] != 0; ++i)
{
printf("[%d] 0x%p ->", i,table[i]);
VFUNC f = (VFUNC)table[i];
f();
}
cout << endl;
}
int main()
{
A a1;
//PrintVFtable((int*)(*((int*)&a1)));
PrintVFtable((long long*)(*((long long*)&a1)));
system("pause");
return 0;
}
4.运行结果如下:
五.在32位和64位平台都可以运行打印虚表
1.我们知道在32位一个指针占4个字节,在64位平台下,一个指针占8个字节,我们只要解引用出来的是一个指针类型就可以达到在32位下面每次都按照4字节遍历,在64位平台下,每次都按照8字节遍历。
2.将代码进行如下修改:
void PrintVFtable(int**table)
{
for (size_t i = 0; table[i] != 0; ++i)
{
printf("[%d] 0x%p ->", i,table[i]);
VFUNC f = (VFUNC)table[i];
f();
}
cout << endl;
}
int main()
{
A a1;
PrintVFtable((int**)(*((int**)&a1)));
system("pause");
return 0;
}
3.上面的代码在64位和32位平台都可以运行。
(1)在64位平台下:
(2)在32位平台下:
上一篇: linux常用命令 linux