离多态更近一步

news/2024/10/23 7:31:15/

在面向对象的语言里面,封装,继承,多态可谓是在熟悉不过了,当我们每次再去重新认识它们的时候总会有新的发现,为此我也经常感到疑惑,所以在这里和大家一起探讨三个问题,让我们在向多态靠近一点点。

虚表是否真的存在静态区

经常我们都会看见一个问题,虚表到底是存放在哪里的。当我们去往上查阅的时候都能出奇的发现一个答案,虚表是存放在静态区中的。对此我曾感到疑惑,存放数据的地方无非有栈,堆,静态区,常量区,首先我们可以排除堆区,因为系统不会自己去开一块空间来使用。接下来我们先了解一下虚表的存放体制,当一个对象在局部域中被创建出来并且它其中也有一张虚表,当局部对象被销毁后在另一个域类定义同一个对象,会发现它们使用的是同一张虚表。

 那么现在还剩下栈区,常量区,静态区,我们可以想一下如果虚表是存放在main函数里面的,那么也是符合条件的,同样,常量区和静态区更不用说。对此我们知道一个观念,相同的数据往往它们存放的地址不会太远,那么我们可以利用这一特征来检验一下看虚表离谁的地址比较近。

  通过这样一段代码我们可以清楚的看到,虚表的地址似乎离静态区相隔很远,但是离常量区似乎很近,那么我们是不是能得出,其实虚表是存放在常量区而并非静态区呢。

派生类中没被重写的虚表去哪里了

首先我们看这样一段代码

 基类中有一个fun1和fun2的虚函数,而派生类有两个不是重写的虚函数

 当我们想去虚表里面找到这个fun3和fun4的时候发现这两个函数并没有在虚表里面,那么问题来了,它们去哪里了呢。我们对此进行更深一步的研究,通过这个虚表去内存里面看看是否能找到消失的它。

 通过对比我们似乎还真找到了这两个地址,但是怎么证明这两个地址就是我们要找的小时的那两个函数呢。

对此我们在回过头来先在了解一下什么是虚函数表。简称虚表,它是由一个指针指向一块空间,空间里面存放了一些地址相连的值,并且这个地址是一个指向函数的指针。那么我们可不可以以认为虚表实际上是一个函数指针数组呢。

 既然这样我们就可以用一个函数指针来取出虚表里面的指针,然后通过这个指针去找到那个函数,对此可以先在函数里面添加一点用于标识的东西。

 我们用这样一段代码来证明我们的猜想。

 通过验证确实得出,那两个地址就是我们消失的fun3和fun4,这就说明,其实并不是不是重写的函数消失了,而是编译器通过特别的方式把它隐藏了起来。

多继承中被重写的虚函数会被放在哪个基类

假设由这样一段代码,一个子类继承成两个父类,那么在子类中,没有被重写的那个虚函数会被放在父类里面呢。

 最好的办法就是去实践它,我们去运行这段代码就会发现,实际上没有被重写的虚函数会被放在base1类里面

 但是我们似乎发现了一个不一样的点,同样是派生类对象D去调用的fun1,为什么调用的地址是不一样的呢。

我们先设置两个基类指针去指向派生类的对象, 然后通过反汇编去看一下具体是什么原因。先通过监视窗口去观察base1的虚函数表是个怎么样的

 可以看到,base1里面第一个第一个虚函地址为0x003f103c,然后主要看反汇编去调用这个函数的那句指令

 看到的是call了这个eax,那么我们再去看一下这个eax是一个什么

 可以看到eax和虚函数表里面第一个地址是一样的,就说明是找到了第一个函数,接下来就要开始调用了,我们按F11(逐语句调试)继续往下一步走

通过这个地址我们找到了fun1,继续往后走

 通过jmp我们拿到了fun1的真正的地址,最后我们得出结论,base1中的调用是正常的,接下来我们继续看看base2的调用

 通过base2同样来到了call这个地址

 eax同样和第一个虚函数同样的,到目前一切为止一切正常,我们继续往下走

同样的是一句jmp,继续往下走 

 来到这里我们就发现这里和上面不一样,当继续jmp的时候它并没有去到fun1的真正地址,而是执行了sub对ecx(*this指针)减去4,实际上是对*this指针减去了4。我们继续往下走

 当我们来到这里就会发现,这个地址好像就是在对base1进行fun1调用的时候出现的地址

 来到这一步我们才发现,最终base2也是调到了fun1但是它饶了一大圈。那么问题又来了,为什么base1不需要绕一大圈呢,这里咱们可以画个图理解一下

首先我们要知道我们要调的是derive的fun1,那么要调derive的fun1就要让指针指向derive对象,base1不用进行操作时因为derive恰好指向derive的开始,而base2却不是,所以要对它进行-4让它指向derive之后才真正开始调用fun1。


http://www.ppmy.cn/news/979670.html

相关文章

【Redis】Redis zset 实现排行榜

文章目录 前言案例程序设计score 设计 (相同积分的排序)缓存数据定时刷新当心缓存击穿 前言 排行榜是业务开发中常见的一个场景,如何设计一个好的数据结构能够满足高效实时的查询,下面我们结合一个实际例子来讨论一下。 案例 大概需求就是: 排行榜上显…

20款奔驰S350商务型加装原厂前排座椅通风系统,夏天必备的功能

通风座椅的主动通风功能可以迅速将座椅表面温度降至适宜程度,从而确保最佳座椅舒适性。该功能启用后,车内空气透过打孔皮饰座套被吸入座椅内部,持续时间为 8 分钟。然后,风扇会自动改变旋转方向,将更凉爽的环境空气从座…

16K个大语言模型的进化树;81个在线可玩的AI游戏;AI提示工程的终极指南;音频Transformers课程 | ShowMeAI日报

👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 🤖 LLM 进化树升级版!清晰展示 15821 个大语言模型的关系 这张进化图来自于论文 「On the Origin of LLMs: An Evolutionary …

【C++ 进阶】继承

一.继承的定义格式 基类又叫父类,派生类又叫子类; 二.继承方式 继承方式分为三种: 1.public继承 2.protected继承 3.private继承 基类成员与继承方式的关系共有9种,见下表: 虽然说是有9种,但其实最常用的还…

无涯教程-jQuery - stopPropagation()方法函数

stopPropagation()方法停止将事件冒泡到父元素,从而防止任何父处理程序收到该事件的通知。 您可以使用方法 event.isPropagationStopped()来了解是否曾经在该事件对象上调用过此方法。 stopPropagation() - 语法 event.stopPropagation() stopPropagation() - …

网络安全进阶学习第七课——文件包含漏洞

文章目录 一、文件包含概念二、文件包含漏洞原理三、文件包含分类文件包含分为两种: 四、与文件包含相关的配置文件:(php.ini文件)五、与文件包含有关的函数1、include()2、include_once()3、require()4、require_once() 六、文件…

FCPX插件-15组金色华丽粒子特效闪耀动画 Awards Backgrounds

Awards Backgrounds是fcpx上一个很棒的电影级效果插件,Awards Backgrounds 包含15组金色华丽粒子特效闪耀动画,可以为您的作品创建豪华的背景或叠加特效!包含各种带有可编辑颜色的下落闪闪发光粒子的场景。用于展示奖项提名者、优雅的表演、祝…

探索NE555:一款经典的集成电路(超详细)

NE555是一款经典的集成电路,它在电子领域被广泛应用于定时器、脉冲发生器、电压控制振荡器等各种应用场景。它的设计简单、易于使用,并且具备稳定可靠的性能,因此深受电子爱好者和工程师的青睐。本篇博客将详细介绍NE555的原理、工作模式和常…