热门标签 | HotTags
当前位置:  开发笔记 > IOS > 正文

C++虚函数表图文解析

最近学了设计模式中的简单工厂模式,对多态有了具体的认识。于是补了补多态、虚函数、虚函数表相关的知识,本文介绍了C++虚函数表,感兴趣的了解一下

一、前言

一直以来,对虚函数的理解仅仅是,在父类中定义虚函数,子类中可以重写该虚函数,并且父类指针可以指向子类对象,调用子类的虚函数(多态)。在读研阶段经历的几个项目中,自己所写的类中并没有用到虚函数,对虚函数这个东西的强大之处并没有太多体会。最近,学了设计模式中的简单工厂模式,对多态有了具体的认识。于是,补了补多态、虚函数、虚函数表相关的知识,参考相关博客,加上自己的理解,整理了这篇博文。

二、含有虚函数类的内存模型

以下面的类为例(32位平台下):

class Father {
public:
	virtual void fun1() { cout <<"Father::fun1()" <

该类中含有两个虚函数和一个成员变量i,输出sizeof(Father),结果为8个字节。如果去掉virtual关键字,则结果为4个字节。也就是说,类中含有虚函数,则该类会增加4个字节,那这4个字节是什么变量所占据的呢?

答案是一个指针(我觉得应该是unsigned int*类型指针,这点不确定),在vs调试窗口中,可以看到该指针名为_vfptr,该指针称为虚函数表指针。

类的内存模型如下,_vptr指针和成员变量i各占4字节,一共8字节。另外 ,-vptr指针一定在内存模型前面。对于只有一个虚表指针的类来说,类内存模型前4个字节就是虚表指针所占空间。

在这里插入图片描述

三、虚函数表与虚函数表指针

上面提到_vfptr是虚函数表指针,那虚函数表是什么呢?

虚函数表其实就是一个指针数组,这个数组中存放着虚函数的地址,大概如下:

在这里插入图片描述

最后一个类似于字符串的结束标志位,VS编译器中为0。

这样的话,虚函数表指针就很容易理解了,这个虚函数表指针指向该虚函数表,也就是虚函数指针的值就是上述指针数组的首地址。

在这里插入图片描述

四、虚函数地址

函数存放在代码区,虚函数也不例外。虚函数表中存放的是虚函数地址,即代码区虚函数的入口地址。

在这里插入图片描述

五、多态

定义一个Father的子类Son,对虚函数fun1()进行重写。

#include
using namespace std;
class Father {
public:
	virtual void fun1() { cout <<"Father::fun1()" <fun1();
	p->fun2();
    p=&son;
    p->fun1();
    p->fun2();
	return 0;
}

父类中有虚函数,则子类同样会有一个虚函数指针,这个指针指向一个新表,如下图所示:

在这里插入图片描述

Son类重写了fun1(),未重写fun2(),那么虚函数表中,第一个地址便是重写的Son::fun1()的地址,第二个地址仍然是父类中Father::fun2()的地址。这里可以在vs调试模式下,查看father与son的虚函数表,son表中第二个元素值与father表中第二个元素值相同。

	Father *p = &father;
	p->fun1();
	p->fun2();

p指向Father对象father:

p->fun1():沿着框1->框3->框5的路径,调用Father::fun1();

p->fun2():沿着框1->框4->框6的路径,调用Father::fun2();

p=&son;
p->fun1();
p->fun2();

p指向子类对象son:

p->fun1():沿着框7->框9->框11的路径,调用Son::fun1();

p->fun2():沿着框7->框10->框6的路径,调用Father::fun2();

六、通过函数指针操作调用虚函数

现修改main函数

typedef void(*Fun)(void);
int main()
{

	Father father;
	Son son;
	printf("虚函数表地址_vfptr:%p\n", *(unsigned int*)(&father));
	printf("第一个虚函数地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father));
	printf("第二个虚函数地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1));

	unsigned char* end = NULL;
	end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2);
	printf("结束符地址d:%p\n", end);
	printf("结束符值:%d\n", *end);

	Fun pFun = NULL;
	pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1));
	pFun();
    
	return 0;
}

运行结果:

在这里插入图片描述

先看一下vs调试模式下各变量的值

在这里插入图片描述

将之前的图修改一下,便于理解:

在这里插入图片描述

红框中,father中的_vfptr为0x1270234,对应上图中的_vfptr,即虚函数表的地址;

数组[0]值为0x01261447,对应上图中的e,即Father::fun1()的地址;

数组[1]值为0x0126141a,对应上图中的f,即Father::fun2()的地址。

好了,现在来看一下程序中这些看起来很唬人的东西:

	printf("虚函数表地址_vfptr:%p\n", *(unsigned int*)(&father));
	printf("第一个虚函数地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father));
	printf("第二个虚函数地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1));
	unsigned char* end = NULL;
	end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2);

这里,直接用上图中的符号进行分析,否则,说一通xx的地址、对xx解引用等等,容易把人搞晕。

(1)虚函数表的地址*(unsigned int*)(&father)

  • a0=&father
  • a1=(unsigned int *)a0
  • _vfptr=*a1

在这里插入图片描述

注意,这里a0和a1的数值是一样的,但只有把地址a0强制转换成(unsigned int *)类型,解引用时系统才会从该地址向后解析4个字节空间,解析成一个unsinged int类型数据。

(2)第一个虚函数地址*(unsigned int*)*(unsigned int*)(&father)

  • b=(unsigned int*)_vfptr
  • e=*b

在这里插入图片描述

(3)第二个虚函数地址*((unsigned int*)*(unsigned int*)(&father)+1)

  • c=b+1
  • f=*c

在这里插入图片描述

(4)结束符地址(unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2)

  • 未强制转换的d=b+2
  • 强制转换的d=(unsigned char*)(b+2)

在这里插入图片描述 

(5)通过函数指针调用虚函数

	Fun pFun = NULL;
	pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1));
	pFun();

在这里插入图片描述

七、结语

这篇博文包含许多自己理解的内容,并在此基础上画了图解,如果有误,还请指正。

八、参考

C++ 虚函数表解析

虚函数表详解

C++ 虚函数详解(虚函数表、vfptr)

到此这篇关于C++ 虚函数表图文解析的文章就介绍到这了,更多相关C++ 虚函数表内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • http:acm.hdu.edu.cnshowproblem.php?pid1846好几天没出题了,今天终于水了一题巴什博弈题。总结:【一】巴什博弈对象:一堆石子(可延伸 ... [详细]
  • 本文介绍了如何使用命令行在 Windows 系统中启动或关闭 VMWare 的关键服务,包括 VMwareHostd、VMAuthdService、VMUSBArbService、VMware NAT Service 和 VMnetDHCP。 ... [详细]
  • KVO(键值观察)是iOS开发中的一项重要技术,它允许一个对象监视另一个对象的属性变化,并在这些属性发生变化时得到通知。KVO特别适用于需要响应模型数据变化的场景。 ... [详细]
  • 题目描述了一个病毒检测问题,要求使用AC自动机算法统计目标文本中多个模式串的出现次数。 ... [详细]
  • 本文旨在介绍在iOS平台进行直播技术开发前的准备工作,重点讲解AVFoundation框架的基本概念和使用方法。通过对AVFoundation的深入理解,开发者能够更好地掌握直播应用中的音视频处理技巧。 ... [详细]
  • 如何在Android和iOS上捕获应用程序崩溃的日志
    本文详细介绍了如何在Android和iOS设备上收集应用崩溃时的日志,包括所需的工具、步骤以及一些常见的注意事项。 ... [详细]
  • 题目概述:给定一棵带颜色节点的树,目标是找到一种方法,通过删除某些边使得每个连通分量内的节点颜色相同。需要计算出所有可能的合法边集的数量。使用动态规划的方法,特别是树形DP来解决问题。 ... [详细]
  • 免费获取:全面更新的Linux集群视频教程及配套资源
    本资源包含最新的Linux集群视频教程、详细的教学资料、实用的学习课件、完整的源代码及多种软件开发工具。百度网盘链接:https://pan.baidu.com/s/1roYoSM0jHqa3PrCfaaaqUQ,提取码:41py。关注我们的公众号,获取更多更新的技术教程。 ... [详细]
  • 本文提供了关于WSDL(Web Services Description Language)的详细参考资料链接,包括官方文档和深入解析,旨在帮助开发者更好地理解和使用WSDL进行Web服务的开发与集成。 ... [详细]
  • 本文探讨了使用React Native框架开发的应用,在通过AppCenter构建iOS版本时遇到的‘CopyPlistFile’命令失败的问题,并提供了详细的解决方案。 ... [详细]
  • 本文旨在深入探讨Java代理模式的基本概念与实际应用场景,通过实例分析如何利用代理模式在不修改原对象的前提下,实现功能的增强或限制。 ... [详细]
  • 本文通过C++代码示例,详细介绍了如何利用邻接矩阵构建无向图,并实现图的深度优先遍历(DFS)。文章包括了完整的代码实现,以及对关键函数的解释。 ... [详细]
  • 本文介绍了一个使用C++编写的简单程序,该程序能在控制台上绘制出一个心形图案,并附带一句温馨的情话。通过调整控制台的颜色设置,使图案更加吸引人。 ... [详细]
  • 本文提供了手势解锁功能的详细实现方法和源码下载链接。通过分析手势解锁的界面和逻辑,详细介绍如何在iOS应用中实现这一功能。 ... [详细]
  • 在使用Firefox浏览器打开本地HTML文件时,尝试调用Canvas的drawImage方法可能会遇到NS_ERROR_NOT_AVAILABLE错误。本文探讨了这一问题的原因及解决方案。 ... [详细]
author-avatar
qinyu1982_469
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有