`
836811384
  • 浏览: 548952 次
文章分类
社区版块
存档分类
最新评论

【C/C++】概念: VC虚函数布局引发的问题

 
阅读更多

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

在打印的时候发现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/C++详细函数大全

    该资源包括了C/C++所有函数详细得介绍和说明,并且带有代码示例,资源来源于培训学校chm

    C/C++中文函数手册

    C/C++中文函数手册,C语言库函数速查手册..

    c/c++ API chm c/c++函数库

    c/c++ API 主要是c/c++函数 c/c++ API 主要是c/c++函数 c/c++ API 主要是c/c++函数 c/c++ API 主要是c/c++函数 c/c++ API 主要是c/c++函数 c/c++ API 主要是c/c++函数 C++ API chm C++ API chm C++ API chm C++ API...

    C/C++编译器(VC,DEV,Codeblocks)

    三合一C/C++编译器,适合初学者:VC:Visual C++ 6.0,简称VC或者VC6.0,是微软的一款C++编译器,将“高级语言”翻译为“机器语言(低级语言)”的程序。;DEV是一个C++ 开发工具。它包括多页面窗口、工程编辑器,在...

    c++虚函数与虚函数表

    学习 C++ 的同志不知道有没有和我一样遇到过这样的困惑:C++中的虚函数到底怎么实现的?在各种继承关系中,虚函数表的结构到底是什么样的?曾经我是很想当然,可是后来在使用ATL的过程中,我发现并不是我想的那样。...

    C/C++ 中文(英文)函数手册

    包括中英文c/c++函数库,函数查找方便,本人经常使用,特别分享出来。

    c / c++ / cpp / stl 中文帮助文档手册chm格式下载

    c / c++ / cpp / stl 中文帮助文档手册chm格式下载 C/C++ 语言参考 基本C/C++ 预处理命令 操作符优先级 ...标准 C 库: ...Standard C I/O ...Standard C String & Character ...全部的 C 函数 全部的 C++ 函数

    C /C++库函数及文件大全 经典 chm

    Character Standard C Math Standard C Time & Date Standard C Memory Other standard C functions All C Functions C++ C++ I/O C++ Strings C++ String Streams ...

    C/C++/Linux函数函数插桩(打桩)指南

    在具有一定规模的代码中(C 语言),调用第三方动态库中的函数来完成一些功能,是很常见的工作场景。 假设现在有一项任务:需要在调用某个动态库中的某个函数的之前和之后,做一些额外的处理工作。 这样的需求一般称作...

    c/c++中文帮助文档(API)

    c/c++中文帮助文档(API),包含c和c++所有的库函数

    C++17 标准 ISOIEC 14882 2017 官方pdf文档

    C++17 标准 ISOIEC 14882 2017 官网 https://www.iso.org/standard/68564.html ISO/IEC 14882:2017 Preview Programming languages -- C++ ISO/IEC 14882:2017 specifies requirements for implementations of the ...

    C/C++:Windows编程—调用DLL程序的2种方法 示例demo

    https://blog.csdn.net/qq_29542611/article/details/86618902 C/C++:Windows编程—调用DLL程序的2种方法 示例demo

    c/c++:strlen源码

    c/c++:strlen源码

    C/C++ 标准库函数 (中文版)

    C/C++ 标准库函数手册。 包含大部分常用的标准库函数、标准模版库和关键字等描述。

    C/C++程序设计学习与实验系统

    原名《Turbo C/C++ for Windows 集成实验与学习环境》,支持最新操作系统WINDOWS 7,它是从事一线教学的大学教师根据C/C++ 初学者的特点,量身定制的一个简单易用的 C/C++程序设计学习与实验软件(支持TC2/TC3、VC6三...

    Tcl/Tk命令与C/C++的集成研究

    Tcl/Tk命令与C/C++的集成研究 针对Tcl/Tk脚本中需要调用C/C++函数的问题,简要说明了Tcl/Tk命令的运行机理,给出了一个使用Tcl/Tk命令来调用C/C++动态链接库(DLL)函数的方案,并给出了将C/C++DLL函数封装为Tcl/Tk C...

    C++ 多态 虚函数 虚函数表 最是详细

    高质量的C++多态讲解,详细讲解虚函数,虚函数表,虚函数继承,虚函数继承下的内存分配等

    密码学:C/C++语言实现(第2版)

    作为一本算法实现的书籍,本书严格遵循软件开发原则,详细描述了设计思想及错误处理方法,并对所有函数进行了广泛测试。 本书可以作为高等院校信息技术相关专业高年级本科生或研究生的教材,也是信息技术从业人员...

    c/c++函数手册,包含了所有的C函数

    c/c++的函数手册,,包含了所有的C函数

    c/c++语句大全;c/c++函数大全

    我自己在网上搜集的,很全的,希望大家喜欢

Global site tag (gtag.js) - Google Analytics