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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值