92 C++对象模型探索。数据语义学 - 指向成员函数的指针,vcall进一步学习

类指针 调用虚函数的时候,会使用 vptr 找虚函数表。

在使用 函数指针  调用成员虚函数的时候会使用到vcall。如果是vcall代码段,则vcall代码会应道编译器找出正确的虚函数表中的虚函数地址进行调用。

一 指向类成员函数的指针,类静态函数,普通全局函数

从前面的学习我们已经知道如何给 定义一个类成员函数指针了.

那么成员函数也是可以通过这种方式定义的,不过有些细节上的不同个,参见代码

class Teacher26base {
public:Teacher26base() {}void Teacher26basefun() {cout << "Teacher26basefun called" << endl;}
};class Teacher26son : public Teacher26base {
public:Teacher26son() {}void Teacher26sonfun() {cout << "Teacher26sonfun called" << endl;}int static Teacher26sonstaticfunc(string str) {cout << "Teacher26sonstaticfunc called str = " << str << endl;return 10;}
};void quanjuTeacher26() {cout << "全局quanjuTeacher26 called" << endl;
}void main() {//旧的回忆,对于全局函数的访问,可以通过定义一个函数指针类型//1.定义一个 函数指针 类型 QuanJuFuncTypetypedef void(*QuanJuFuncType)();//2.使用类型  定义 变量 func,由于是一个指针,QuanJuFuncType func = quanjuTeacher26;func();//3.直接定义一个函数指针类型void(*jutiQuanJuFunc)();jutiQuanJuFunc = &quanjuTeacher26;//jutiQuanJuFunc = quanjuTeacher26;//对于全局函数,前面加不加&都一样jutiQuanJuFunc();//4.对于类成员函数,我们可以这么干吗,是的,也可以这么干,不过前面要加上类命名空间void(Teacher26son::*jutiQuanJuFuncTeacher26son)();//4.1 但是在类成员指针的赋值的时候,一定要加上&jutiQuanJuFuncTeacher26son = &Teacher26son::Teacher26sonfun;//4.2 调用时,需要一个类对象,//注意调用的时候,是(对象.*xxx)(),这是为了和类成员函数 tea.xxx区分Teacher26son tea;(tea.*jutiQuanJuFuncTeacher26son)();//5.如果是类静态成员函数int(*staticfunc)(string tempvalue) = &Teacher26son::Teacher26sonstaticfunc;//静态成员也是一样的,不同的是,需要加上类命名空间,注意的是和 全局函数一样,前面加不加 &都可以,加不加& 是历史原因。int result = staticfunc("abc");cout << "result = " << result  << endl;}

二 指向虚成员函数的指针以及vcall进一步谈

如果要弄一个 指向类成员虚函数呢?

class Teacher26base {
public:Teacher26base() {}void Teacher26basefun() {cout << "Teacher26basefun called" << endl;}int virtual Teacher26basevirtualfunc(double dou) {cout << "Teacher26basevirtualfunc called dou = " << dou << endl;int inttemp = (int)dou;return inttemp;}int virtual Teacher26basevirtualfunc2(double dou) {cout << "Teacher26basevirtualfunc2 called dou = " << dou << endl;int inttemp = (int)dou * 2;return inttemp;}
};class Teacher26son : public Teacher26base {
public:Teacher26son() {}void Teacher26sonfun() {cout << "Teacher26sonfun called" << endl;}int static Teacher26sonstaticfunc(string str) {cout << "Teacher26sonstaticfunc called str = " << str << endl;return 10;}int virtual Teacher26basevirtualfunc(double dou) {cout << "Teacher26sonvirtualfunc called dou = " << dou << endl;int inttemp = (int)dou;return inttemp;}int virtual Teacher26basevirtualfunc2(double dou) {cout << "Teacher26sonvirtualfunc2 called dou = " << dou << endl;int inttemp = (int)dou * 2;return inttemp;}};void quanjuTeacher26() {cout << "全局quanjuTeacher26 called" << endl;
}void main() {//旧的回忆,对于全局函数的访问,可以通过定义一个函数指针类型//1.定义一个 函数指针 类型 QuanJuFuncTypetypedef void(*QuanJuFuncType)();//2.使用类型  定义 变量 func,由于是一个指针,QuanJuFuncType func = quanjuTeacher26;func();//3.直接定义一个函数指针类型void(*jutiQuanJuFunc)();jutiQuanJuFunc = &quanjuTeacher26;//jutiQuanJuFunc = quanjuTeacher26;//对于全局函数,前面加不加&都一样jutiQuanJuFunc();//对于类成员函数,我们可以这么干吗,是的,也可以这么干,不过前面要加上类命名空间void(Teacher26son::*jutiQuanJuFuncTeacher26son)();jutiQuanJuFuncTeacher26son = &Teacher26son::Teacher26sonfun;//但是在类成员指针的赋值的时候,一定要加上&//调用时,需要一个类对象,Teacher26son tea;(tea.*jutiQuanJuFuncTeacher26son)();//注意调用的时候,是(对象.*xxx)(),这是为了和类成员函数 tea.xxx区分//如果是静态成员函数int(*staticfunc)(string tempvalue) = &Teacher26son::Teacher26sonstaticfunc;//静态成员也是一样的,不同的是,需要加上类命名空间,注意的是和 全局函数一样,前面加不加 &都可以,加不加& 是历史原因。int result = staticfunc("abc");cout << "result = " << result  << endl;//5.如果是虚函数呢?由于虚函数也是依赖于 类对象调用的,也就是也需要this指针//  虚函数是如下的两个:int virtual Teacher26sonvirtualfunc(double dou) //	int virtual Teacher26sonvirtualfunc2(double dou) //5.1 定义如下int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;//5.2 使用 类指针 调用 虚函数指针类型Teacher26base *ptea26 = new Teacher26son();result = (ptea26->*vir)(3.14);00A6F46E  mov         esi,esp  
00A6F470  sub         esp,8  
00A6F473  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F47B  movsd       mmword ptr [esp],xmm0  
00A6F480  mov         ecx,dword ptr [ptea26]  
00A6F483  call        dword ptr [vir]  
00A6F486  cmp         esi,esp  
00A6F488  call        __RTC_CheckEsp (0A61596h)  
00A6F48D  mov         dword ptr [result],eaxcout << "result1 = " << result << endl;//5.3 使用 类对象 调用  虚函数指针类型Teacher26base tea26;result = (tea26.*vir)(78.8);00A6F4D7  mov         esi,esp  
00A6F4D9  sub         esp,8  
00A6F4DC  movsd       xmm0,mmword ptr [__real@4053b33333333333 (0A79F98h)]  
00A6F4E4  movsd       mmword ptr [esp],xmm0  
00A6F4E9  lea         ecx,[tea26]  
00A6F4EC  call        dword ptr [vir]  
00A6F4EF  cmp         esi,esp  
00A6F4F1  call        __RTC_CheckEsp (0A61596h)  
00A6F4F6  mov         dword ptr [result],eax cout << "result2 = " << result << endl;//5.4 通过 类指针 正常调用 虚函数ptea26->Teacher26basevirtualfunc(3.14);00A6F538  mov         esi,esp  
00A6F53A  sub         esp,8  
00A6F53D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F545  movsd       mmword ptr [esp],xmm0  
00A6F54A  mov         eax,dword ptr [ptea26]  
00A6F54D  mov         edx,dword ptr [eax]  
00A6F54F  mov         ecx,dword ptr [ptea26]  
00A6F552  mov         eax,dword ptr [edx]  
00A6F554  call        eax  
00A6F556  cmp         esi,esp  
00A6F558  call        __RTC_CheckEsp (0A61596h)  //5.5 通过 类对象 正常调用 虚函数tea26.Teacher26basevirtualfunc(23);00A6F55D  sub         esp,8  
00A6F560  movsd       xmm0,mmword ptr [__real@4037000000000000 (0A79F88h)]  
00A6F568  movsd       mmword ptr [esp],xmm0  
00A6F56D  lea         ecx,[tea26]  
00A6F570  call        Teacher26base::Teacher26basevirtualfunc (0A61442h)//5.6 通过类对象 正常调用普通函数tea26.Teacher26basefun();00A6F575  lea         ecx,[tea26]  
00A6F578  call        Teacher26base::Teacher26basefun (0A61749h)  //5.6 那么这个5.2,   5.3,   5.4有区别吗?debug看反汇编的代码cout << "断点在这里" << endl;
}

结论:

1. 不管是 类对象,还是 类指针 调用 虚函数指针 都是会走 虚函数指针 查找 虚函数表。

但是这个两种方式调用 虚函数的具体步骤 会有不同。

类指针 调用虚函数的时候,会使用 vptr 找虚函数表。

在使用函数指针调用成员虚函数的时候会使用到vcall。

//5.1 定义如下int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;//5.2 使用 类指针 调用 虚函数指针类型Teacher26base *ptea26 = new Teacher26son();result = (ptea26->*vir)(3.14);00A6F46E  mov         esi,esp  
00A6F470  sub         esp,8  
00A6F473  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F47B  movsd       mmword ptr [esp],xmm0  
00A6F480  mov         ecx,dword ptr [ptea26]  
00A6F483  call        dword ptr [vir]  
00A6F486  cmp         esi,esp  
00A6F488  call        __RTC_CheckEsp (0A61596h)  
00A6F48D  mov         dword ptr [result],eaxcout << "result1 = " << result << endl;//5.3 使用 类对象 调用  虚函数指针类型Teacher26base tea26;result = (tea26.*vir)(78.8);00A6F4D7  mov         esi,esp  
00A6F4D9  sub         esp,8  
00A6F4DC  movsd       xmm0,mmword ptr [__real@4053b33333333333 (0A79F98h)]  
00A6F4E4  movsd       mmword ptr [esp],xmm0  
00A6F4E9  lea         ecx,[tea26]  
00A6F4EC  call        dword ptr [vir]  
00A6F4EF  cmp         esi,esp  
00A6F4F1  call        __RTC_CheckEsp (0A61596h)  
00A6F4F6  mov         dword ptr [result],eax 

看到:    result = (ptea26->*vir)(3.14); 通过 指向类虚函数指针的方法 调用该函数,也会有vcall的出现

00A6F4EC  call        dword ptr [vir]     //在这一步的时候,按F11 就会发现004D187F  jmp         Teacher26base::`vcall'{0}' (04D776Eh)  ,然后再F11,就会发现会走到这里:004D1799  jmp         Teacher26son::Teacher26basevirtualfunc (04D7C60h) 

2. 类指针 调用虚函数 会走 虚函数指针 查找 虚函数表

	//5.4 通过 类指针 正常调用 虚函数ptea26->Teacher26basevirtualfunc(3.14);00A6F538  mov         esi,esp  
00A6F53A  sub         esp,8  
00A6F53D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F545  movsd       mmword ptr [esp],xmm0  
00A6F54A  mov         eax,dword ptr [ptea26]  
00A6F54D  mov         edx,dword ptr [eax]  
00A6F54F  mov         ecx,dword ptr [ptea26]  
00A6F552  mov         eax,dword ptr [edx]  
00A6F554  call        eax  
00A6F556  cmp         esi,esp  
00A6F558  call        __RTC_CheckEsp (0A61596h)  

3.  类对象调用虚函数类对象调用 普通函数  会有不同个,但是都不会走

	//5.5 通过 类对象 正常调用 虚函数tea26.Teacher26basevirtualfunc(23);00A6F55D  sub         esp,8  
00A6F560  movsd       xmm0,mmword ptr [__real@4037000000000000 (0A79F88h)]  
00A6F568  movsd       mmword ptr [esp],xmm0  
00A6F56D  lea         ecx,[tea26]  
00A6F570  call        Teacher26base::Teacher26basevirtualfunc (0A61442h)//5.6 通过类对象 正常调用普通函数tea26.Teacher26basefun();00A6F575  lea         ecx,[tea26]  
00A6F578  call        Teacher26base::Teacher26basefun (0A61749h)  

4. 类对象调用虚函数没有 继承。

发现是和有继承的情况下是一样的。都是5步完成。

public:int virtual Teacher27virtualfunc(double dou) {cout << "Teacher27virtualfunc called dou = " << dou << endl;int inttemp = (int)dou;return inttemp;}
};void main() {Teacher27 tea;tea.Teacher27virtualfunc(3.14);0028A37A  sub         esp,8  
0028A37D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0299F78h)]  
0028A385  movsd       mmword ptr [esp],xmm0  
0028A38A  lea         ecx,[tea]  
0028A38D  call        Teacher27::Teacher27virtualfunc (0281906h) cout << "end" << endl;
}

5. 在如下的代码中打印 虚函数的地址。

也会看到  vcall的使用

	printf("Teacher26base:: %p\n",&Teacher26base::Teacher26basevirtualfunc);//	00E8F5C6  push        offset Teacher26base::`vcall'{0
//}' (0E8187Fh)  
//00E8F5CB  push        offset string "Teacher26base:: %p\n" (0E991E8h)
//00E8F5D0  call        _printf(0E81091h)
//00E8F5D5  add         esp, 8

总结:

什么时候会使用到vcall

在使用函数指针调用成员虚函数的时候会使用到vcall

三 vcall 在继承中的体现

    int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;
    int(Teacher26son::*vir1)(double tempdou) = &Teacher26son::Teacher26basevirtualfunc;

如上的两行代码,都是定义一个 函数指针,且是类的虚函数指针,到这里,我们应该有个意识,这两个里面都会使用到vcall代码段,vcall里面应该都存储的有 偏移,且由于Teacher26basevirtualfunc是第一个虚函数,因此偏移应该是0才对。

那么再调用的时候,我们一定要一个类指针,或者类引用

    Teacher26base *ptea26 = new Teacher26son();
    result = (ptea26->*vir)(3.14);
    cout << "result1 = " << result << endl;

指针和引用由于是动态绑定,因此等号右边是啥,ptea26就会指向的是啥。

因此当前case下,this就是 Teacher26son,有了this,有了偏移,就能找到对应的真正的函数,调用一下就可以找到真正的函数地址了。


    

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

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

相关文章

MySQL索引的原理和SQL优化策略

1. 索引 在InnoDB存储引擎中&#xff0c;索引分为聚簇索引和辅助索引两种类型。 聚簇索引是指基于表的主键构建的索引&#xff0c;它决定了表中数据的物理存储顺序。也就是说&#xff0c;聚簇索引中的键值按照主键的顺序来排序&#xff0c;并且每个叶子节点存储的是整个表行的…

2024美赛A题思路/代码:资源可用性和性别比例

美赛直播b站&#xff0c;提前关注&#xff1a;川川菜鸟 美赛辅导预定&#xff1a;美赛服务 去年美赛A题作品&#xff1a;2023美赛A题 题目 背景 尽管一些动物物种不属于通常的雄性或雌性&#xff0c;大多数物种在出生时要么显著地为雄性&#xff0c;要么为雌性。虽然许多物…

HAL库配置PWM模式

一、什么是PWM 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&#xff0c;简称脉宽调制。通过控制高低电平在一个周期内的占比从而输出一定的电压。 向上计数原理介绍 ​PWM的一个周期 定时器从0开始向上计数 当0-t1段,定时器计数器TIMx_CNT值小于…

直播团队职责

一、内容策划 直播团队的内容策划人员是整个直播活动的核心&#xff0c;他们需要负责策划直播的主题、内容、形式以及时间安排等。同时&#xff0c;他们还需要负责邀请嘉宾、安排活动等&#xff0c;确保直播内容丰富、有趣、有价值。 二、主播管理 主播是直播活动的关键人物…

unity WebGL发布游戏生成WebGL

1.unty Hub中安装WEBGL支持 2.项目平台的切换 color space需要根据项目选择 ColorSpace&#xff0c;是指玩家设置的颜色空间。 伽马颜色空间是历史悠久的标准格式&#xff0c;但线性颜色空间渲染可提供更精确的结果。 具体区别&#xff1a;ColorSpace 3.由于没有自己服务器…

壹[1],Xamarin开发环境配置

1&#xff0c;环境 VS2022 注&#xff1a; 1&#xff0c;本来计划使用AndroidStudio&#xff0c;但是也是一堆莫名的配置让人搞得很神伤&#xff0c;还是回归C#。 2&#xff0c;MAUI操作类似&#xff0c;但是很多错误解来解去&#xff0c;且调试起来很卡。 3&#xff0c;最…

Spring声明式事务

1.概念 事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行&#xff0c;要么完全地都不执行&#xff0c; 它是一个不可分割的工作执行单元 一个使用Mybatis-Spring的主要原因是它允许Mybatis参与到Spring的事务管理中&#xff0c;而不是给Mybatis创建一个新的…

如果你也觉得自己不够聪明,也缺乏才华。。。

​在追求成功的道路上&#xff0c;我们常常自我怀疑&#xff0c;感觉自己不够聪明&#xff0c;缺乏必要的才华。然而&#xff0c;正是这种自我感知&#xff0c;如果处理得当&#xff0c;可以成为我们最大的优势。这篇文章旨在为那些怀疑自己的能力&#xff0c;但依然渴望在工作…

图片热区功能

一、需求描述及效果图 1.需求描述&#xff1a; 根据后端返回的坐标及人员信息&#xff0c;在图片上的相应位置添加图片热区功能&#xff0c;点击可展示出对应的人员信息。 图片可进行缩放 2.示例&#xff1a; &#xff08;定位是随便写的&#xff0c;仅做示例&#xff09; …

Mac用Crossover玩《幻兽帕鲁》手柄不能用怎么办? Mac电脑玩《幻兽帕鲁》怎么连接手柄? 幻兽帕鲁玩家超1900万

2024年首款爆火Steam平台的游戏《幻兽帕鲁》&#xff0c;在使用Crossover后可以用Mac系统玩了&#xff0c;很多玩家喜欢通过手柄玩游戏&#xff0c;它拥有很好的握持体验&#xff0c;长时间玩也不会很累&#xff0c;所以很多《幻兽帕鲁》玩家都喜欢用手柄来操作&#xff0c;很多…

Docker 容器jar 运行报错 at sun.awt.FontConfiguration.getVersion 解决方法

docker jar 运行报错 at sun.awt.FontConfiguration.getVersion 初步判断是在运行 Docker 容器中的 JAR 文件时遇到了与字体配置相关的问题。这个问题可能是由于容器内缺少字体配置或字体文件而引起的。 要解决这个问题&#xff0c;你可以尝试以下方法&#xff1a; 1.安装字…

史上最全知识图谱建模实践(下):多元关系架构

在“知识图谱之本体结构与语义解耦——基于OpenSPG的建模实践&#xff08;上&#xff09;”一文中&#xff0c;我们从实体关系设计和概念语义建模2种场景&#xff0c;讲解了基于SPG的知识建模的方法和案例。 本文中&#xff0c;我们将继续讲解多元关系架构场景中的知识建模实践…

构建云安全防线:企业必备的10大能力解析

云计算技术为现代企业组织带来了可扩展性、灵活性、减少物理基础设施、降低运营成本以及全天候的数据访问等诸多好处。但研究数据也显示&#xff0c;目前只有4%的企业组织能够为云端资产提供充分的安全保护。在2023年&#xff0c;有超过80%的数据泄露事件涉及存储在云端的数据。…

Vue3基本概念

script部分 export default对象的属性&#xff1a; name&#xff1a;组件的名称 components&#xff1a;存储中用到的所有组件 props&#xff1a;存储父组件传递给子组件的数据 watch()&#xff1a;当某个数据发生变化时触发 computed&#xff1a;动态计算某个数据 setup(pro…

json文件缺少的语言key的检测

需求 在做多语言的项目的时候&#xff0c;需要对当前不同语言的key的差异进行对比并且找出缺少key的语言和具体的语言key 通过node的文件读取能力进行需求的实现 const fs require(fs) const path require(path); const lodash require(lodash); // 目的是找出多语言文件中…

基于C/C++的MFC的IDC_MFCEDITBROWSE2控件不显示ico问题记录

打开资源文件 *.rc文件 &#xff0c;在最上方添加 #if !defined(_AFXDLL) #include "afxribbon.rc" // MFC ribbon and control bar resources #endif 如下图所示&#xff1a;

解决pandas写入excel时的ValueError: All strings must be XML compatible报错

报错内容&#xff1a; ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters 报错背景 用pands批量写入excel文件&#xff0c;发生编码报错。检索了很多方案&#xff0c;都不能解决。 导致报错的原因是存在违法字符&…

米贸搜|Facebook公共主页反馈分数(ACE) 更新

前段时间Meta改进了公共主页反馈分数的仪表板&#xff0c;发现有部分广告主似乎没有接受到这条动态&#xff0c;今天为大家整理出更新内容&#xff0c;方便各位广告主了解学习&#xff01; Meta重新设计了公共主页反馈分数仪表板&#xff0c;以便广告主能更轻松地了解总体反馈…

【INTEL(ALTERA)】为什么 F-tile Serial Lite IV FPGA IP 设计示例会失败

说明 由于Intel Agilex 7 FPGA I 系列收发器-SoC 开发套件的时钟控制器 GUI 存在问题&#xff0c;当您需要配置芯片 Si5332 的 OUT1 时钟频率时&#xff0c;您可能会发现 F-tile Serial Lite IV 英特尔 FPGA IP设计示例失败。这是因为此 Si5332 GUI 存在问题;无法准确配置 OUT…

8. Threejs案例-SVG渲染器和WEBGL渲染器对比

8. Threejs案例-SVG渲染器和WEBGL渲染器对比 实现效果 知识点 SVG渲染器 (SVGRenderer) SVGRenderer 被用于使用 SVG 来渲染几何数据&#xff0c;所产生的矢量图形在以下几个方面十分有用&#xff1a; 动画标志 logo 或者图标 icon可交互的 2D 或 3D 图表或图形交互式地图复…