【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++】概念: VC虚函数布局引发的问题的更多相关文章
- 从汇编层面深度剖析C++虚函数
文章出处:http://blog.csdn.net/linyt/article/details/6336762 虚函数是C++语言实现运行时多态的唯一手段,因此掌握C++虚函数也成为C++程序员是否合 ...
- C++中的继承与虚函数各种概念
虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数
一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...
- C++虚函数及虚函数表解析
一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Functio ...
- sdut 6-2 多态性与虚函数
6-2 多态性与虚函数 nid=24#time" title="C.C++.go.haskell.lua.pascal Time Limit1000ms Memory Limit ...
- C++ 多态与虚函数
1.多态的概念 由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应. 先看下面这个简单的例子: #include<iostream> using std:: ...
- mfc 虚函数
知识点 类虚函数概念 类虚函数定义virtual 一.虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数. 二.虚函数定义 定义:在某基类中声明为 virtual 并在一个或多个派 ...
- C++ ------ 虚函数覆盖、重载
在C++语言中,虚函数是非常重要的概念,虚函数是实现C++面向对象中多态性和继承性的基石.而多态性和继承性则是面向对象语言的精髓.掌握虚函数才算是真正掌握C++语言,而C++语言中虚函数的继承覆盖与函 ...
- C++开发系列-纯虚函数和抽象类
概念 纯虚函数和抽象类 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都实现该函数. 纯虚函数为各派生类提供了一个公共界面(接口的封装和设计.软件的模块功能的划分) 纯虚函数说明 ...
随机推荐
- Python 官方团队在打包项目中踩过的坑
花下猫语:这是 packaging 系列的第三篇译文,该系列是全网关于此话题的最详尽(水平也很高)的一个系列.原作者是 Python 官方打包团队成员,是 virtualenv 和 tox 项目的维护 ...
- 分享在开发多终端使用比较多的Adb命令
分享在开发多终端或者涉及PC-Android的传输使用比较多的Adb命令 查看连接的设备 adb devices 列出设备安装的软件包 adb shell pm list packages 使用这个方 ...
- K8S中的ingress-nginx配置
编辑在有道云笔记中,链接如下: http://note.youdao.com/noteshare?id=8d9ae3a1643e39714b36b58f7507baa2&sub=61C8C20 ...
- 小技巧(2) 查询自己博客的SEO(如果违规,请先提醒)
今天,教大家查看自己博客的SEO. 首先,上网站http://seo.chinaz.com/ 接着,在网页输入框中输入自己博客地址(带后面路径,不是cnblogs.com这样的,类似https://w ...
- 使用docker增加部署速度的一次实践
问题: 公司给我们分配的服务器到期后不付费了,换成新服务商的服务器了.也就是说我们之前的环境需要重新搭建一次.光项目就50多个(微服务40+,其他服务不到10个),需要重新部署. 之前部署项目时,需要 ...
- 0x80070035找不到网络路径
如果这个报错发生:自己的网络正常,其他人可以正常访问服务器,而自己无法访问服务器.原因就是TCP/IP NetBIOS Helper服务被停止. 打开services.msc,启动此服务即可. 该服务 ...
- 简单易学的Linux 命令整理
Linux 是目前应用最广泛的服务器操作系统,基于 Unix,开源免费,由于系统的稳定性和安全性,市场占有率很高,几乎成为程序代码运行的最佳系统环境.linux 不仅可以长时间的运行我们编写的程序代码 ...
- AI漫谈:我们距离实现《庆余年》里的五竹叔机器人还有多远?
(警告: 本文包含少量剧透内容,请酌情阅读) 五竹叔是机器人吗? 看过庆余年的朋友,一定对五竹叔印象深刻,外表英俊潇洒,一袭黑衣加黑布条蒙眼,充满神秘侠客气息.五竹叔不但神秘,而且言行举止常常很 ...
- D2. Optimal Subsequences (Hard Version) 主席树
题目链接:https://codeforces.com/contest/1262/problem/D2 将数组按大到小排序(相同大小的按下标由小到大排序),依次将排序后的每个数在原数组中的位置放入主席 ...
- IDEA 公司推出新字体,极度舒适~
这几天炒得沸沸扬扬的 Intellij IDEA 公司 JetBrains 推出了一种新字体:JetBrains Mono,据说它是专为开发人员设计的,下面栈长带大家一起来吃个瓜. JetBrains ...