从汇编的角度了解C++原理——虚函数

news2024/11/26 2:32:26

文章目录

  • 1、虚函数
    • 1.1、虚函数储存结构
    • 1.2、子类重写虚函数
    • 1.3、在栈上调用虚函数
    • 1.4、在堆上调用虚函数(通过指针调用,多态)

本文用到的反汇编工具是objconv,使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642。

其它文章:
从汇编的角度了解C++原理——类的储存结构和函数调用
从汇编的角度了解C++原理——new和malloc的区别
从汇编的角度了解C++原理——虚函数

1、虚函数

1.1、虚函数储存结构

在这里插入图片描述
反汇编。

main:
        sub     rsp, 56                             
        lea     rcx, [rsp+20H]                    
        call    ??0A@@QEAA@XZ      				//调用构造函数                  
        mov     eax, 4294967295                  
        add     rsp, 56                                
        ret                                             
        
??0A@@QEAA@XZ:									//调用A类的构造函数
        mov     qword [rsp+8H], rcx                  
        mov     rax, qword [rsp+8H]                    
        lea     rcx, [rel ??_7A@@6B@]           //获取虚表??_7A@@6B@的地址
        mov     qword [rax], rcx                //把虚表地址放在对象的头部      
        mov     rax, qword [rsp+8H]                    
        mov     dword [rax+8H], 10              //在对象首地址偏移8个字节的位置定义d1变量       
        mov     rax, qword [rsp+8H]                     
        ret                                                           

??_7A@@6B@:                     				//A类虚表                       
        dq ?func2@A@@UEAAXXZ                    //虚函数func2      

以上例的汇编代码可以得出带虚函数的类的储存结构如下图所示。
在这里插入图片描述
带有虚函数的对象的头部会放置8个字节大小的虚表地址,有了虚表之后的对象会以8个字节为单位去对齐,如上例中的A类,如果没有虚函数,它的大小为4个字节,而加了虚函数之后,大小变为了16个字节。

1.2、子类重写虚函数

在代码中添加A的子类B,重写func2方法。
在这里插入图片描述

反汇编

main:
        sub     rsp, 56                             
        lea     rcx, [rsp+20H]                       
        call    ??0B@@QEAA@XZ          		//调用B类构造                
        mov     eax, 4294967295                        
        add     rsp, 56                                
        ret                                             
        
??0A@@QEAA@XZ:								//A类构造函数
        mov     qword [rsp+8H], rcx                  
        mov     rax, qword [rsp+8H]                     
        lea     rcx, [rel ??_7A@@6B@]     	//把A类虚表的地址放在头部              
        mov     qword [rax], rcx                      
        mov     rax, qword [rsp+8H]                   
        mov     dword [rax+8H], 10                  
        mov     rax, qword [rsp+8H]           
        ret                                             

??0B@@QEAA@XZ:								//B类构造函数
        mov     qword [rsp+8H], rcx                
        sub     rsp, 40                             
        mov     rcx, qword [rsp+30H]               
        call    ??0A@@QEAA@XZ    			//调用A类构造                   
        mov     rax, qword [rsp+30H]             
        lea     rcx, [rel ??_7B@@6B@]       //把B类虚表的地址放在头部           
        mov     qword [rax], rcx                    
        mov     rax, qword [rsp+30H]                   
        add     rsp, 40                                 
        ret                                           
       
??_7A@@6B@:                         		//A类虚表                        
        dq ?func2@A@@UEAAXXZ                //A::func2                             
        dq ?func3@A@@UEAAXXZ                //A::func3      
             
??_7B@@6B@:                                 //B类虚表                       
        dq ?func2@B@@UEAAXXZ                //B::func2,被替换为了B实现的func2          
        dq ?func3@A@@UEAAXXZ                //A::func3                         

从该例中我们可以看到,父类有虚函数时,不光它自己有一张虚表,它的子子孙孙都会各带有一个自己的虚表,子类重写虚函数时,会把子类实现的函数指针替换上虚表,把原先父类的函数指针覆盖掉。

1.3、在栈上调用虚函数

在main里添加方法的调用。
在这里插入图片描述
反汇编。

main:
        sub     rsp, 56                               
        lea     rcx, [rsp+20H]                         
        call    ??0B@@QEAA@XZ                        
        lea     rcx, [rsp+20H]                        
        call    ?func1@A@@QEAAXXZ   		//调用A::func1                 
        lea     rcx, [rsp+20H]                        
        call    ?func2@B@@UEAAXXZ   		//调用B::func2                       
        lea     rcx, [rsp+20H]                         
        call    ?func3@A@@UEAAXXZ   		//调用A::func3                         
        mov     eax, 4294967295                         
        add     rsp, 56                                
        ret   

在栈上调用方法时,因为类型是确定的,所以编译器在编译阶段就会找到对应的函数去调用,调用过程与普通方法一样。

1.4、在堆上调用虚函数(通过指针调用,多态)

修改例程如下。
在这里插入图片描述
反汇编

main:
        sub     rsp, 72                                
        mov     ecx, 16                                
        call    ??2@YAPEAX_K@Z                         
        mov     qword [rsp+28H], rax                   
        cmp     qword [rsp+28H], 0                    
        jz      ?_001                                 
        mov     rcx, qword [rsp+28H]		//定义指针b                   
        call    ??0B@@QEAA@XZ                       
        mov     qword [rsp+30H], rax        //rsp+30H指向对象          
        jmp     ?_002                       //跳到?_002            

?_001:  mov     qword [rsp+30H], 0   
                  
?_002:  mov     rax, qword [rsp+30H]                   
        mov     qword [rsp+38H], rax        //rsp+38H指向对象         
        mov     rax, qword [rsp+38H]        //rax指向对象           
        mov     qword [rsp+20H], rax        //rsp+20H指向对象       
        mov     rcx, qword [rsp+20H]        //rcx指向对象          
        call    ?func1@A@@QEAAXXZ           //调用A::func1       
        mov     rax, qword [rsp+20H]                 
        mov     rax, qword [rax]            //取虚表        
        mov     rcx, qword [rsp+20H]                   
        call    near [rax]                  //执行虚表第一个函数,即B::func2           
        mov     rax, qword [rsp+20H]                   
        mov     rax, qword [rax]                       
        mov     rcx, qword [rsp+20H]                
        call    near [rax+8H]             	//执行虚表第二个函数,即A::func3             
        mov     eax, 4294967295                         
        add     rsp, 72                               
        ret                                          
        
??_7B@@6B@:                                           
        dq ?func2@B@@UEAAXXZ                        
        dq ?func3@A@@UEAAXXZ                          

从该例可以看到,通过指针来调用函数时。
如果是普通函数,编译器会直接根据指针类型,找到对应的的方法,而不是根据对象本身的类型,如本例中B类也实现了func1方法,但通过A类指针调用时,写到汇编里的时A::func1。
如果是虚函数,编译器不会根据名字来查找函数,而是让汇编代码通过虚表中的偏移量来调用,如本例中,b指针执行了func2和func3,这两个函数都没有被直接调用,而是以“call near [rax + 偏移量]”的形式调用了,这也是C++中父类指针指向子类对象的多态的实现原理。

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

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

相关文章

编译基于armV8架构的opencv,并在rock3a开发板上运行

近期在基于arm开发板做图像识别任务开发时,需要用到Opencv库 之前在做rknpu开发时,开发sdk里面已经集成了opencv 但是该opencv开发包不能实现imshow/VideoCapture等函数,经过调研,决定对opencv源代码进行编译,生成arm…

安科瑞能耗监测系统在新疆昌吉市政务中心项目的研究与应用

安科瑞 华楠摘要:大型公共建筑总面积不足城镇建筑总面积的4%,但总能耗却占全国城镇总耗电量的22%,大型公共建筑单位面积年耗电量达到70~300KWh,为普通居民住宅的10~20倍。公共建筑是节能大户和节能核心&…

Delphi 11.2 安装 CnWizards 组件包

官方网址:https://www.cnpack.org/showdetail.php?id900&langzh-cn 开源免费的组件包,大大提高了开发效率,再次感谢大佬们的无私奉献 这个组件包主要是为了实现一些delphi没有的便捷设置,以及能给delphi增加了一些好用的辅助…

惊艳的产品背后,是锐利的设计思维

缘起 几年前,我偶然用一个 叫 Zine 的小app 写了两篇文章,感觉非常好。 后来在网上认识 了Zine 团队的创始人 Louis,也喜欢上了他们的另一个 App:Varlens, 最近他们推出了记笔记的 App Lattics,一些功能也…

《 Unity Shader 入门精要》 第3章 Unity Shader 基础

第3章 Unity Shader 基础 3.1 Unity Shader 概述 材质与 Unity Shader 在 Unity 中我们通常需要将材质(Material) 和 Unity Shader 配合使用,常见流程如下: 创建一个材质创建一个 Unity Shader, 并将它赋给材质将材…

Android View类

布局定义了应用中的界面结构(例如 Activity 的界面结构)。布局中的所有元素均使用 View 和 ViewGroup 对象的层次结构进行构建。View 通常用于绘制用户可看到并与之交互的内容。ViewGroup 则是不可见的容器,用于定义 View 和其他 ViewGroup 对…

AppScan自定义扫描策略,扫描针对性漏洞

系列文章 AppScan介绍和安装 AppScan 扫描web应用程序 AppScan被动手动探索扫描 AppScan绕过登录验证码深入扫描 第五节-AppScan自定义扫描策略,扫描针对性漏洞 AppScan安全扫描往往速度是很慢的,有些场景下他的扫描项目又不是我们需要的,…

如何实现六轴机械臂的逆解计算?

1. 机械臂运动学介绍 机械臂运动学 机器人运动学就是根据末端执行器与所选参考坐标系之间的几何关系,确定末端执行器的空间位置和姿态与各关节变量之间的数学关系。包括正运动学(Forward Kinematics)和逆运动学(Inverse Kinemati…

渔业养殖远程监控系统解决方案

传统的水产养殖依靠养殖者的经验进行观察,信息不准确,调控不及时,养殖规模扩大难,人工成本高。除此之外传统水产养殖以个户居多,生产管理方式粗放,个体生产能力不足,养殖产品的品质难以保障。 …

AppScan扫描报告

系列文章 AppScan介绍和安装 AppScan 扫描web应用程序 AppScan被动手动探索扫描 AppScan绕过登录验证码深入扫描 AppScan自定义扫描策略,扫描针对性漏洞 第六节-AppScan扫描报告 1.加载扫描结果 1.点击【打开】 2.选择之前保存过的扫描结果 3.等待加载完成 …

RK35XX(3568) Android WSL ubuntu22.04 编译环境配置

前言:装Ubuntu真机操作是很流畅但是没什么软件,装Vmware虚拟机操作卡顿配置也麻烦。那不如试一试wsl吧,命令行操作,流程又快捷wsl简介:适用于 Linux 的 Windows 子系统可让开发人员按原样运行 GNU/Linux 环境 - 包括大…

JAVA面试(如何进行有效面试)

1、什么是面试它是一种面试人与求职者之间相互交流信息的有目的的会谈。它使招聘方和受聘方都能得到充分的信息,以在招聘中作出正确的决定。面试是一个双方彼此考量和认知的过程。2、面试的目标从求职者那里获取与个人行为、工作有关的信息,以确定求职者…

c语言数组复习

1、一维数组 ----------&#xff08;1&#xff09;、键盘输入 10 个数&#xff0c;求最大值和最小值&#xff08;最简单的方法&#xff09; ----------&#xff08;2&#xff09;、数组的逆置 #include<stdio.h> void test01() {int arr[10] { 0 };int n sizeof(arr)…

【IoT】硬件选型:如何正确区分电子线的端子型号?

问题提出 笔者最近负责的一款重力传感器由于没有端子&#xff0c;需要在生产时自己压端子&#xff0c;这个时候就会涉及端子的选择。 端子介绍 一般来说&#xff0c;端子有多种不同的型号&#xff0c;在使用的时候&#xff0c;你必须要注意到每种型号之间的差别。 端子一般有XH…

权限管理---尚硅谷

1.项目基础 2.定义统一返回结果对象 3.Nodejs 4.前端内容编写 5.菜单详情 6.SpringSecurity权限管理 7.添加登录日志 8.操作日志 9.后端打包 10.前端打包 11.动态sql日期的判断 项目基础 定义统一返回结果对象定义全局统一返回结果类 import lombok.Data;/*** 全局统一返回结果…

小程序容器技术助力突破智能汽车瓶颈

作为一种综合系统&#xff0c;智能车辆集环境感知、规划决策、多等级辅助驾驶等功能于一体。近年来&#xff0c;智能车辆已经成为世界车辆工程领域研究的热点和汽车工业增长的新动力&#xff0c;很多发达国家都将其纳入到各自重点发展的智能交通系统当中。在共享经济兴起和汽车…

如何写好JS

本节课从实践维度解读在实际编码过程中何种类型的 JavaScript 代码称之为“好代码”&#xff0c;并从 JS 出发&#xff0c;总结其他语言编码可遵循的共性原则&#xff0c;由浅入深&#xff0c;其三大原则是&#xff1a; 各司其职——html&#xff0c;css&#xff0c;js分离组件…

通达信接口QQ是什么端口?

可以将通达信接口QQ视为使用通达信市场软件作为数据库&#xff0c;然后将信息整合为策略的前提&#xff0c;所有行为都是自动化的。通达信接口的优势在于交易策略是事先制定的。是否基于市场波动&#xff0c;不受个人情绪的影响&#xff0c;可以大大降低个人原因造成的错误。 …

[ACTF2020 新生赛]BackupFile

目录 信息收集 思路 构造payload 知识补充 信息收集 从题目来看应该是让扫描备份文件(backupfile) 进入页面就一句话 Try to find out source file! 先用dirbuster模糊扫描一下目录 常见的如下 index.phps index.php.swp index.php.swo index.php.php~ index.php.bak ind…

有哪些数据恢复软件?13个好用的数据恢复工具分享

个人编辑开发了此资源&#xff0c;以帮助购买者寻找最好的免费数据恢复软件来满足其组织的需求。选择合适的供应商和工具可能是一个复杂的过程&#xff0c;需要深入研究&#xff0c;而且往往不仅仅取决于工具及其技术能力。为了让您的搜索更轻松一些&#xff0c;我们在一个地方…