首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

VC虚函数布局引发的问题

  • 25-03-02 17:20
  • 3355
  • 9029
blog.csdn.net

  在网上看到一个非常热的帖子,里面是这样的一个问题:

     在打印的时候发现pFun的地址和 &(Base::f)的地址竟然不一样太奇怪了?经过一番深入研究,终于把这个问题弄明白了。下面就来一步步进行剖析。

 

      根据VC的虚函数的布局机制,上述的布局如下:

        然后我们再细细的分析第一种方式:

         Fun pFun = (Fun)*((int*)*(int*)(d)+0);

d是一个类对象的地址。而在32位机上指针的大小是4字节,因此*(int*)(&d)取得的是vfptr,即虚表的地址。从而*((int*)*(int*)(&d)+0)是虚表的第1项,也就是Base::f()的地址。事实上我们得到了验证,程序运行结果如下:

        

这说明虚表的第一项确实是虚函数的地址,上面的VC虚函数的布局也确实木有问题。

      但是,接下来就引发了一个问题,为什么&(Base::F)和PFun的值会不一样呢?既然PFun的值是虚函数f的地址,那&(Base::f)又是什么呢?带着这个问题,我们进行了反汇编。

printf("&(Base::f): 0x%x /n", &(Base::f));

    00401068  mov         edi,dword ptr [__imp__printf (4020D4h)] 

    0040106E  push        offset Base::`vcall'{0}' (4013A0h) 

    00401073  push        offset string "&(Base::f): 0x%x /n" (40214Ch) 

    00401078  call        edi  

printf("&(Base::g): 0x%x /n", &(Base::g));

    0040107A  push        offset Base::`vcall'{4}' (4013B0h) 

    0040107F  push        offset string "&(Base::g): 0x%x /n" (402160h) 

    00401084  call        edi  

那么从上面我们可以清楚的看到:

     Base::f 对应于Base::`vcall'{0}' (4013A0h) 

     Base::g对应于Base::`vcall'{4}' (4013B0h)

 

那么Base::`vcall'{0}'和Base::`vcall'{4}'到底是什么呢,继续进行反汇编分析

Base::`vcall'{0}':

    004013A0  mov         eax,dword ptr [ecx] 

    004013A2  jmp         dword ptr [eax]  

      ......

Base::`vcall'{4}':

    004013B0  mov         eax,dword ptr [ecx] 

    004013B2  jmp         dword ptr [eax+4] 

     第一句中, 由于ecx是this指针, 而在VC中一般虚表指针是类的第一个成员, 所以它是把vfptr, 也就是虚表的地址存到了eax中. 第二句

相当于取了虚表的某一项。对于Base::f跳转到Base::`vcall'{0}',取了虚表的第1项;对于Base::g跳转到Base::`vcall'{4}',取了虚表第2项。由此都能够正确的获得虚函数的地址。

      由此我们可以看出,vc对此的解决方法是由编译器加入了一系列的内部函数"vcall". 一个类中的每个虚函数都有一个唯一与之对应的vcall函数,通过特定的vcall函数跳转到虚函数表中特定的表项。

 

     更深一步的进行讨论,考虑多态的情况,将代码改写如下:

 

 

打印的时候表现出来了多态的性质:

分析可知原因如下:

      这是因为类Derive的虚函数表的各项对应的值进行了改写(rewritting),原来指向Based::f()的地址变成了指向Derive::f(),原来指向Based::g()的地址现在编变成了指向Derive::g()。

反汇编代码如下:

printf("&(Derive::f): 0x%x /n", &(Derive::f));

    00401086  push        offset Base::`vcall'{0}' (4013B0h) 

    0040108B  push        offset string "&(Derive::f): 0x%x /n" (40217Ch) 

    00401090  call        esi  

printf("&(Derive::g): 0x%x /n", &(Derive::g));

    00401092  push        offset Base::`vcall'{4}' (4013C0h) 

    00401097  push        offset string "&(Derive::g): 0x%x /n" (402194h) 

    0040109C  call        esi 

 

      因此虽然此时Derive::f依然对应Base::`vcall'{0}',而 Derive::g依然对应Base::`vcall'{4}',但是由于每个类有一个虚函数表,因此跳转到的虚表的位置也发生了改变,同时因为进行了改写,虚表中的每个slot项的值也不一样。

 

稍微总结一下:

 

在VC中有两种方法调用虚函数,一种是通过虚表,另外一种是通过vcall thunk的方式

 

通过虚表的方式:

       base *d = new Derive;

       d->f();

               004115FA  mov         eax,dword ptr [d]
              004115FD  mov         edx,dword ptr [eax] 
              004115FF  mov         esi,esp 
              00411601  mov         ecx,dword ptr [d]
              00411604  mov         eax,dword ptr [edx]
              00411606  call        eax 
              00411608  cmp         esi,esp
              0041160A  call        @ILT+470(__RTC_CheckEsp) (4111DBh)

 这种方式的应用环境是通过类对象的指针或引用来调用虚函数

 

通过vcall thunk的方式:

       typedef void (Base::* func1)( void );

       base *d = new Derive;

       func1 pFun1 = &Base::f;

       (d->*pFun1)();

                004115A9  mov         dword ptr [pFun1],offset Base::`vcall'{0}' (4110C3h)
                004115B0  mov         esi,esp
                004115B2  lea          ecx,[d]
                004115B5  call          dword ptr [pFun1]
                004115B8  cmp         esi,esp
                004115BA  call        @ILT+460(__RTC_CheckEsp) (4111D1h)
 

  这种方式对应的应用环境是通过类成员函数的指针来调用虚函数

 

 

文章知识点与官方知识档案匹配,可进一步学习相关知识
C技能树函数与程序结构函数的声明与定义156665 人正在系统学习中
注:本文转载自blog.csdn.net的zhanglei8893的文章"http://blog.csdn.net/zhanglei8893/archive/2011/04/19/6333751.aspx"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top