【C++】 为什么多继承子类重写的父类的虚函数地址不同?『 多态调用汇编剖析』

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


前言

本篇文章主要是为了解答有关多态的那篇文章那块的一个奇怪现象,大家还记得这张图片么?

你有没有发现:子类重写的func1函数地址竟然是不同的?

按常理讲:我们知道函数地址存储的是函数的指令的位置,这里『 应该是相同』的,才能保证对象在调用时都调用『 子类重写后的』func1方法 ,否则就失去了重写的意义了。

所以这里一定存在某些底层设计,那接下来就让我们转到『反汇编 』,来查看以下vs在这里是如何设计的吧。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.构建模型

首先,为了方便研究,我们构建函数模型:

class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1=1;
};class Base2 {
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }
private:int b2=2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1=3;
};int main()
{Derive d;// 多态调用Base1* p1 = &d;p1->func1();Base2* p2 = &d;p2->func1();return 0;
}

2.剖析

通过内存窗口我们得出这样的结构:

经过多态部分的学习,我们知道p1指针指向的对象内存中『 0x00819b94 』就是Base1的虚表指针,同样的p2指针指向的对象内存中『 0x00819ba8』就是Base2的虚表指针。

监视窗口也可以看出来这些:

注意:多态那篇文章我们已经提到过,vs的监视窗口这里有一个bug,就是没能显示出子类func3函数, 即子类d的虚函数表没有显示在监视窗口中,这里大家可以参考我多态部分的文章有详解,你也可以自己通过内存窗口验证。子类的虚函数表添加在继承的第一个父类的虚表后。

当然以上说的不是我们这篇文章的重点,只是一个回顾,更多详见『 樊梓慕』多态 - CSDN

我们主要研究func1函数的地址为什么是不同的? 

接下来我们转到反汇编:

2.1Base1类型的p1指针调用func1 

首先观察下p1调用func1的汇编代码,看看call到了哪里?

寄存器eax中存储的是『 0x00811230』,我们接着走:

很明显是一个jmp指令,再继续:

到这,我们就成功跳转到了子类重写的func1函数。

这也是正常情况下函数调用的过程。

那接下来我们来研究p2指针调用func1又是怎样的过程呢?

2.2Base2类型的p2指针调用func1

同样的eax中存储的地址是什么呢?继续往下走:

 同样是一个jmp指令,再继续:

这里jmp到了给ecx减8,然后再jmp,在ecx减8之前,我们先来看看ecx中存储的是什么:

注意:成员函数的调用中,寄存器ecx通常用来存储this指针

那减去8之后,很明显就变成了『 0x00fcfba0』,这个地址是什么呢?

其实就相当于子类Derive的this指针。

到这其实已经非常明显了,那我们继续看jmp到了哪里:

到这里,你有没有发现这步的jmp和Base1类型的p1指针调用func1的jmp已经完全一样了,继续:

好,成功调用到func1函数。


3.总结

总结一下,Base2类型的p2指针调用func1函数时多做了一些工作,多jmp了一步,jmp的这一步目的是为了调整this指针,让this指针指向子类的头部。

为什么呢?

  • 因为p1和p2都是父类指针指向子类对象,p1是因为巧合恰好与子类头部位置重合,所以this指针位置本就是正确的,不需要额外操作。
  • 而p2的this指针指向的位置是子类中自己的虚表位置,所以需要额外jmp一步,使p2指针指向的子类对象的this指针进行一定的偏移,让this指针到达正确的位置,才能完成调用func1的操作。

😈剖析底层,修炼内功😈


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/684748.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序框架阐述

目录 一、框架 响应的数据绑定 页面管理 基础组件 丰富的 API 二、逻辑层 App Service 小程序的生命周期 注册页面 使用 Page 构造器注册页面 在页面中使用 behaviors 使用 Component 构造器构造页面 页面的生命周期 页面路由 页面栈 路由方式 注意事项 模块化…

鸿蒙开发系列教程(二十二)--List 列表操作(1)

列表是容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。 用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本 List、ListItemGroup、ListItem关系 列表方向 1、概念 列表的主轴方向是指子组件列的排列方…

【汇总】解决IndexedDB报Failed to execute ‘transaction‘ on ‘IDBDatabase‘

问题发现 再学习HTML5中&#xff0c;有介绍到 Web 存储&#xff0c;当代码编写完成后&#xff0c;运行报错 Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found. 示例代码如下&#xff1a; <!DOCTYPE html> <…

1Coze平台介绍

2023年随着OpenAI推出GPT 3.5&#xff0c;AI赛道变得更加火热。GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种自然语言处理&#xff08;NLP&#xff09;模型&#xff0c;用于生成文本、理解语言和进行各种语言任务。GPT是由OpenAI开发的&#xff0c;它…

Tuxera NTFS2024版本的文件操作功能有哪些特点?

Tuxera NTFS通过集成先进的文件系统驱动程序和算法&#xff0c;实现了对多种文件系统的全面支持。具体来说&#xff0c;它具备以下功能和特点&#xff0c;使其能够支持多种文件系统&#xff1a; Tuxera NTFS2024下载如下: https://wm.makeding.com/iclk/?zoneid58824 先进的…

shumei 滑块 qd参数仿写记录

在对qd参数进行仿写的过程中&#xff0c;由于缺失很多js的基础知识&#xff0c;导致进展一度非常的缓慢&#xff0c;并且不知道自己的方向是不是正确的方向。在不知道自己的方向是否正确的时候&#xff0c;这个时候自己的投入的努力都是畏首畏尾。大概是一种&#xff0c;不知道…

实验5-6 使用函数判断完全平方数

本题要求实现一个判断整数是否为完全平方数的简单函数。 函数接口定义&#xff1a; int IsSquare( int n ); 其中n是用户传入的参数&#xff0c;在长整型范围内。如果n是完全平方数&#xff0c;则函数IsSquare必须返回1&#xff0c;否则返回0。 裁判测试程序样例&#xff1…

Python 播放音乐

本篇是使用Python pygame库来实现操作音乐。 安装pygame 播放音乐需要pygame库&#xff0c;如果没有可以进行安装。 命令如下&#xff1a; pip install pygame 引入类库 需要引入两个类库&#xff0c;即time和pygame。 示例如下&#xff1a; import time import pygame 播…

基于剪贴板的文件传输方案

文章目录 背景原理步骤获取待上传文件的十六进制数据格式转换输出 背景 某次误删了环境上的C库之后想到的 什么都不可用了&#xff0c;但当前的ssh连接还在&#xff0c;echo命令和重定向符还可以使用 这就催生了我的想法&#xff1a;直接用echo -en “\xab\xcd” > file这样…

20240215-查找周长最大的多边形

题目要求 给你一个由长度为 n 的正整数 nums 组成的数组。 多边形是至少有 3 条边的封闭平面图形。多边形的最长边小于其他边之和。 相反&#xff0c;如果有 k 个&#xff08;k > 3&#xff09;正实数 a1、a2、a3、...、ak&#xff0c;其中 a1 < a2 < a3 < ... …

【c++】析构函数

1.特征 析构函数是特殊的成员函数&#xff0c;其特征如下&#xff1a; 1.析构函数名是在类名前加上字符~。 2.无参数无返回值类型。 3.一个类只能有一个析构函数。若未显式定义&#xff0c;系统会自动生成默认的析构函数。注意&#xff1a;析构函数不能重载。 4.对象生命周…

StringBuffer线程安全而StringBuilder线程不安全

下面我们从append方法的源码进行分析 StringBuilder Overridepublic StringBuilder append(String str) {super.append(str);return this;} StringBuffer Overridepublic synchronized StringBuffer append(String str) {toStringCache null;super.append(str);return this…

H12-821_48

48.下面是台路由器输出的BGP信息,关于这段信息描述措误的是 A.路由器的Router ID是1.1.1.9 B.display bgp network命令来显示BGP通过network ( BGP)的通告的路由信息 C.该路由器所在AS号是10 D.该路由器通过import-route命今引入了4.4.4.0/24的网段 答案&#xff1a;D 注释&am…

假期作业 10

1.整理磁盘操作的完整流程&#xff0c;如何接入虚拟机&#xff0c;是否成功识别&#xff0c;对磁盘分区工具的使用&#xff0c;格式化&#xff0c;挂载以及取消挂载 U盘接入虚拟机 在虚拟机--->可移动设备--->找到U盘---->连接 检测U盘是否被虚拟机识别 ls /dev/s…

Diffusion Transformer U-Net for MedicalImage Segmentation

用于医学图像分割的扩散变压器U-Net 摘要&#xff1a; 扩散模型在各种发电任务中显示出其强大的功能。在将扩散模型应用于医学图像分割时&#xff0c;存在一些需要克服的障碍:扩散过程调节所需的语义特征与噪声嵌入没有很好地对齐;这些扩散模型中使用的U-Net骨干网对上下文信…

Codeforces Round 926 (Div. 2)(A~C)

A. Sasha and the Beautiful Array 分析&#xff1a;说实话&#xff0c;打比赛的时候看到这题没多想&#xff0c;过了一下样例发现将数组排序一下就行&#xff0c;交了就过了。刚刚写题解反应过来&#xff0c;a2-a1a3-a2.....an-a(n-1) an - a1&#xff0c;所以最后结果只取决…

103.二叉树的锯齿形层序遍历

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 每一层从左到右遍历&#xff0c;逐一判断是否需要反转。 解题代码&#xff1a; /*** Definition for a bina…

Python字典全面指南:基础操作与高级应用实例解析

在Python编程中&#xff0c;字典作为核心的数据结构之一&#xff0c;凭借其高效查询、动态添加与删除属性&#xff0c;成为了处理键值对数据的理想选择。本文将逐步详解Python字典的基本操作&#xff0c;并通过实际案例展示如何查找共同键、设置默认值、更新字典、合并字典&…

python工具方法 45 基于ffmpeg以面向对象多线程的方式实现实时推流

1、视频推流 参考基于ffmpeg模拟监控摄像头输出rtsp视频流并opencv播放 实现视频流的推流。 其基本操作就是,安装视频流推流服务器,ffmpeg,准备好要推流的视频。 命令如下所示:ffmpeg -re -stream_loop -1 -i 风景视频素材分享.flv -c copy -f rtsp rtsp://127.0.0.1:554/…

Java数组,你想要的重点都在这。

1.数组的概述 数组 , 就可以理解为多个多个数据的组合 是程序中的容器 : 数组 , 集合框架(以后会将 : List , Set , Map) 数组存储中的数据的特点 : 依次紧密排列 , 有序的 , 可以重复的 此时的数组 , 集合框架都是在内存中对多个数据的存储 . 数组的其他特点 : 一旦初始化…