空对象它有一字节的大小,在没有任何成员变量但是却有虚函数的对象里,它的大小是四个字节,这是为什么呢?


因为含有虚函数的对象里,对象的起始地址往后四个字节其实是 一个指针,它指向了一个数组,这个数组的元素是 指针,这些数组的元素指向的地方就是虚函数实现的地方,我们称这个数组叫做 虚表。而指向这个虚表的指针我们成为 虚表指针。这个对象为什么是4个字节的大小,也就是含有这个虚表指针。

对于开发者而言,虚表指针都是隐藏的,在常规的开发过程中,我们感受不到它们的存在,要想感知它们的存在,看看底层的汇编代码即可,或者通过c的形式,来实现虚函数的间接调用。

对象中的虚表指针和虚表的关系如下所示:

虚表指针的初始化过程:



40108D~401090把虚表指针放在this指针所指的地方(也就是对象的起始地址),也就是对象开始的前四个字节存放的是虚表指针。

对象的虚表指针初始化是通过编译器在构造函数内插入代码来完成的,在用户没有编写构造函数时,由于必须初始化虚表指针,因此编译器会提供默认的构造函数,以完成虚表指针的初始化。
(这里也就印证了我们之前所发博客 (提供默认构造函数的第一点))

注意:
a.由于虚表信息在编译后会被链接到对应的执行文件中,因此所获得的虚表地址是一个相对固定的地址。
b.虚表中虚函数的地址的排列顺序依据虚函数在类中的声明顺序而定,先声明的虚函数的地址会被排列在虚表中靠前的位置。即第一个被声明的虚函数的地址在虚表的首地址处

虚表的元素所指向的函数是如何被进行调用的呢?
解释:
在虚表指针的初始化过程中,对象执行了构造函数后,就得到了虚表指针,当其它代码访问到这个对象的虚函数时,会根据对象的首地址,取出对应虚表元素。当函数被调用时,会间接访问虚表,得到对应 的虚函数首地址,并调用执行(记住,虚表指针是放在对象里面的,对象,对象,对象

虚表间接寻址寻址访问的情况只有在使用对象的指针或引用来调用虚函数的时候才会出现;如果直接使用对象来调用自身的虚函数时,没必要查表访问,因为已经明确调用的是自身成员函数,根本没有构成多态问题。

接下来我们看看如何调用自身类中的虚函数的:


它直接通过对象去调用了自身的成员函数,因此编译器使用了直接调用函数的方式,并未去访问虚表指针,并没有间接获取虚表指针。

仔细分析虚表指针后发现,编译器隐藏了虚表指针的初始化实现代码,当类中出现虚函数时,必须在构造函数中对虚表指针执行初始化操作,而没有虚函数的类对象在构造时,不会进行初始化虚表指针的操作。因此,在分析构造函数时,又增加了一个特性:虚表指针初始化。
如果排除开发者伪造编译器生成的代码来误导分析人员的可能,我们就可以给出一个结论:对于单线继承的类结构,在其某个成员函数中,将this的地址初始化虚表首指针时,那么我们可以断定,这个成员函数就是构造函数。

下面来分析一下含有虚表的对象析构函数



两者对虚表操作几乎相同,都是将虚表指针设置成当前对象所属类中的虚表首地址。两者看似相同,其实差别很大。
构造函数中完成的是初始化虚表指针的工作,执行前**虚表指针并没有指向虚表地址,**而执行析构函数时,其对象的虚表指针已经指向了某个虚表指针。

大家是否觉得这里是不是很没有必要呢?这里实际上是还原虚表指针,让其指向自身的虚表首地址,防止在析构函数中调用虚函数时取到非自身的虚表,从而导致函数调用错误
这里也就是前面博客所提到的,在构造函数和析构函数里面会进行虚表覆盖。不会产生多态。)

总结:
如何识别虚函数:
a.类中隐式定义了一个数据成员
b.该数据成员在首地址处,占4字节
c.构造函数会将此数据成员初始化为某个数组的首地址
d.这个地址属于数据区,是一个相对固定的地址
e.在这个数组内,每个数组成员都是函数指针
f.仔细观察这些函数,它们被调用时,第一个参数必然是this指针(要注意调用约定)
g.在这些函数内部,很有可能会对this指针使用相对间接的访问方式

虚函数系列:
详解虚函数的实现过程之初探虚表(1)
详解虚函数的实现过程之单继承(2)
详解虚函数的实现过程之多重继承(3)
详解虚函数的实现过程之虚基类(4)
详解虚函数的实现过程之菱形继承(5)

详解虚函数的实现过程之初探虚表(1)相关推荐

  1. 详解虚函数的实现过程之菱形继承(5)

    大家看到标题,会不会菱形继承的虚表会不会是重复的呢?祖父类的虚表会不会在子类会不会是两份相同呢?那么我们一起来探索一下吧,冲冲冲!! 首先我们来分析一下: 它一共定义了四个类,分别为CFurnitur ...

  2. 详解虚函数的实现过程之虚基类(4)

    博客虚函数实现过程3 时提到过虚基类,这里呢,我们来详细讲述一下: 当我们在虚函数的声明结尾处添加"=0",这种虚函数就被称为纯虚函数. 它好似一个没有实现只有声明的函数,它的存在 ...

  3. 详解虚函数的实现过程之多重继承(3)

    下面来一起探索一下多重继承时,有虚函数会怎么继承呢? 这里大家猜一下,SofaBed会占多少个字节呢? 首先我们是不是得猜一下它有几个虚表指针? 4* 4(4个int数据)+2*4(两个虚表指针)=2 ...

  4. 详解虚函数的实现过程之单继承(2)

    从汇编分析一下下面的多态模拟结构 利用 父类指针指向子类的特性,可以间接调用各子类中的虚函数. 虽然指针类型为父类,但由于虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的,因此,只要继承 ...

  5. 详解虚函数的实现过程之菱形继承修罗场(6)

    在这里想跟大家一起来探索一下菱形继承的类对象如何来执行构造函数,以及析构函数 这展示了子类CSofaBed的构造过程,它的特别之处是在调用时要传入一个参数,这个参数是一个标志信息.构造函数中要先构造父 ...

  6. python中可变参数*args传入函数时的存储方式为_python 中文读法详解Python函数可变参数定义及其参数传递方式...

    Python函数可变参数定义及其参数传递方式详解 python中 函数不定参数的定义形式如下 1. func(*args) 传入的参数为以元组形式存在args中,如: def func(*args): ...

  7. 一分钟详解initUndistortRectifyMap函数bug修复方法

    本文首发于微信公众号「3D视觉工坊」--一分钟详解initUndistortRectifyMap函数bug修复方法 在上一篇文章OpenCV中initUndistortRectifyMap函数存在bu ...

  8. python命名空间和闭包_Python函数基础实例详解【函数嵌套,命名空间,函数对象,闭包函数等】...

    本文实例讲述了Python函数基础用法.分享给大家供大家参考,具体如下: 一.什么是命名关键字参数? 格式: 在*后面参数都是命名关键字参数. 特点: 1.约束函数的调用者必须按照Kye=value的 ...

  9. 详解Scala函数也是对象的特性

    详解Scala函数也是对象的特性

最新文章

  1. 4698: Sdoi2008 Sandy的卡片
  2. 计算勒让德多项式的系数
  3. BLE蓝牙hid键盘表
  4. 三家逐鹿,私有化部署能帮神策数据杀出重围么?| 公司调研
  5. 新款苹果手机_苹果宣布新系统 性能依旧“压制quot;安卓
  6. android car bt模块,大谷蓝牙小车BT Car/Android Car手机控制 重力控制小车 安卓操控小车...
  7. 男27,想转行互联网,是学习软件测试好,还是前端编程?
  8. IntelliJ IDEA 2018.2.2及以下版本破解方法
  9. matplotlib —— 注释及几何图形的绘制
  10. python进不去怎么办_python写文件有时候写不进去怎么办
  11. 中国各省公共财政收入与公职人员数量(2012-2019年)
  12. c盘瘦身 或者 c盘清理
  13. 农历和阳历的之间的转换
  14. android模拟anr,Android ANR
  15. ubuntu终端下字体放大缩小快捷键
  16. 结构化数据和非结构化数据
  17. 【树莓派】登入树莓派
  18. 工业机器人码垛教学实施_工业机器人码垛方案设计
  19. 服务器操作系统tco的英文全称,云服务器ECS的英文全称
  20. 水仙花数(c语言程序实现)

热门文章

  1. 成功解决安装cuda的时候,下载的文件自动消失,并且出现An unknown error has occurred
  2. 成功解决TypeError: __init__() got an unexpected keyword argument 'n_iterations'
  3. EL之GB(GBC):利用GB对二分类问题进行建模并评估
  4. 元计算:《元计算破解生命密码》听课笔记
  5. 虚拟机 centos 6.5 扩展根目录分区大小
  6. Python2 Python3 爬取赶集网租房信息,带源码分析
  7. tomcat更改端口
  8. Thread Group(线程组)
  9. zabbix查看数据
  10. Linux usual cmd