欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ -- 探索虚函数表

程序员文章站 2024-03-17 00:02:22
...

一.虚函数表

1.虚函数:类的成员函数前面加上virtual关键字。

2.虚函数表:用来存放虚函数地址的一张表。

3.虚函数表就像一张地图,指明了要调用的虚函数。

二.虚函数表的小知识

1.一个类中只要含有虚函数,则一定会生成一张虚表。

(1)一个类中含有虚函数,其对象监视窗口如下,可以看到A类的对象a1中含有一个虚表指针(_vfptr),这个虚表指针指向了一个虚表,虚表里面含有两个元素,func1和func2函数的地址:

C++ -- 探索虚函数表

(2)一个类中不含有虚函数,则其对象的监视窗口如下(可以看到A类的对象a就只含有成员变量,没有虚表指针):

C++ -- 探索虚函数表

2.对象里面并不是存放整个虚表的,对象里面只存放了一个指向虚表的指针。(由上面图可以看出,对象里面只存放一个虚表指针_vfptr)

  • 为什么不存放整个虚表?

如果一个对象里面存放整个虚表,不仅会使该对象变得很大,而且会使得与其同类的所有对象里面都存放一个虚表,但是每个对象的虚表都是相同的,为了节省空间还有提高效率,应该让同类的所有对象指向同一个虚表,这样只需要每个对象里面存放一个指向虚表的指针就可以了。

3.同类型的所有对象共用一块虚表。

例如:在静态区定义一个A的对象a3,在栈上定义两个A的对象a1和a2,通过监视窗口可以看到A的所有对象a1,a2,a3里面的虚表指针的内容都相同,都是0x003acc74,表明他们的虚表的地址都相同,所以他们共用同一块虚表。

C++ -- 探索虚函数表

三.通过自己编写的程序打印虚表

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.运行结果如下:

C++ -- 探索虚函数表

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)运行结果如下:

C++ -- 探索虚函数表

四.在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.运行结果如下:

C++ -- 探索虚函数表

五.在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位平台下:

C++ -- 探索虚函数表

(2)在32位平台下:

C++ -- 探索虚函数表

 

相关标签: 虚函数 虚表