c++多态及虚函数表内部原理实战详解

2023-11-07

1.多态实现方式

c++的多态机制主要是靠虚函数来实现。具体来说,就是用父类的指针指向子类的实例,然后通过父类指针调用子类对象中的成员函数。这样,就实现了父类指针的“多态"。

想了解虚函数实现机制,就必须先了解对象的存储方式。

2.类的存储方式

我们以为的存储方式是这样:在这里插入图片描述
上面的图,表示对象的数据和函数代码都要分配内存空间,这样内存的利用效率显然较低,因此实际上存储方式是这样:
在这里插入图片描述
每个对象占用存储空间的只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分),函数代码属于公用部分。我们常说的“A对象的成员函数,是从逻辑的角度而言的,而成员函数的物理存储方式其实不是如此。

3.c++内存分区

C++的内存分区大概分成五个部分:

1.栈(stack):是由编译器在需要时自动分配,不需要时自动清除的变量存储区,通常存放局部变量、函数参数等。
2.堆(heap):是由malloc等分配的内存块,和堆十分相似,用free来释放
3.自由存储区:是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应,如果程序员没有释放掉,资源将由操作系统在程序结束后自动回收
4.全局/静态存储区:全局变量和静态变量被分配到同一块内存中
5.常量存储区:这是一块特殊存储区,里边存放常量,不允许修改

你可能会问:静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,因而可以说它们都是属于类的,但是类为什么只能直接调用静态类成员函数,而非静态类成员函数(即使函数没有参数)只有类对象才能调用呢

原因是:类的非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因此只有类对象才能调用(此时this指针有实值)

4.虚函数表

每个包含虚函数的类都含一个虚函数表 Virtual Table,简称为VTable。在c++中,编译器会保证虚函数表的指针会存在对象实例最前面的位置。于是我们可以通过对象实例地址得到这张虚函数表,然后遍历其中函数指针,就可以调用相应的函数。

总结起来就是以下几点:
1.每个包含虚函数的类都有一个虚函数列表。
2.虚函数表的指针存在对象实例最前面的位置。
3.派生类虚表的虚函数地址排列顺序与基类中虚函数地址排列顺序完全一致。如果子类中包含有自身虚函数,会排列在后面。
4.虚表可以继承。如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。

5.通过代码来验证虚表原理

首先我们再次明确一下:虚函数表的指针存在对象实例最前面的位置(目前各个编译器基本都是是这样设置)

假设我们有Base类

class Base {
    public:
        virtual void f() {cout<<"base::f"<<endl;}
        virtual void g() {cout<<"base::g"<<endl;}
        virtual void h() {cout<<"base::h"<<endl;}
};

我们试图通过Base类的实例来得到虚函数表。

void virtual_func() {
    typedef void(*Fun)(void);
    Base b;
    Fun pFun = NULL;
    cout << "虚函数表地址:" << (long*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (long*)*(long*)(&b) << endl;
    pFun = (Fun)*((long*)*(long*)(&b));
    pFun();
    pFun=(Fun)*((long*)*(long*)(&b)+1);
    pFun();
    pFun=(Fun)*((long*)*(long*)(&b)+2);
    pFun();
    pFun=(Fun)*((long*)*(long*)(&b)+3);
    // pFun(); 会报错
    cout<<pFun<<endl; // 此内存区域现在不知道是啥,会输出一随机值。

    
}

int main(int argc, char const *argv[])
{
    virtual_func();
    return 0;
}

上面的代码我们逐句解读一下

typedef void(*Fun)(void);

定义了一个函数指针,该函数指针的参数为void (没有),返回类型为void。

重点看看这句(long*)*(long*)(&b):
&b,取b的首地址,根据我们前面所说,即为虚函数表的地址。然后我们将其(long*)(&b),强制转换为long型指针,也就是指向虚函数表这个数组中首元素地址的指针。
注意(long*)(&b)本身是一个指针,里面存的数据也是指针,即虚表数组首元素地址。因此,我们对(long*)(&b)这个数组取值,再转成long型指针,得到的就是虚表首元素,即第一个虚函数Base::f()的地址了。

pFun = (Fun)*((long*)*(long*)(&b));
又因为pFun是由Fun这个函数声明的函数指针,所以相当于是Fun的实体,必须再将这个地址转换成pFun认识的类型,即加上(Fun)*进行强制转换。

整个过程简单来说,就是从bObj地址开始读取四个字节的内容(&bObj),然后将这个内容解释成一个内存地址((long*)(&bObj)),再访问这个地址((long*) * (long*)(&bObj)),最后将这个地址中存放的值再解释成一个函数的地址((Fun) * ((long*) * (long*)(&bObj)))

代码的最终输出如下:

虚函数表地址:0x7ffeeb8b5328
虚函数表 — 第一个函数地址:0x10435c070
base::f
base::g
base::h
1

套用一张图,我们就能比较清晰地看明白上面的过程
在这里插入图片描述

参考文献

https://www.cnblogs.com/zhxmdefj/p/11594459.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++多态及虚函数表内部原理实战详解 的相关文章

  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 按成员序列化

    我已经实现了template
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • BitTorrent 追踪器宣布问题

    我花了一点业余时间编写 BitTorrent 客户端 主要是出于好奇 但部分是出于提高我的 C 技能的愿望 我一直在使用理论维基 http wiki theory org BitTorrentSpecification作为我的向导 我已经建
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 重载<<的返回值

    include
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • 哪种 C 数据类型可以表示 40 位二进制数?

    我需要表示一个40位的二进制数 应该使用哪种 C 数据类型来处理这个问题 如果您使用的是 C99 或 C11 兼容编译器 则使用int least64 t以获得最大的兼容性 或者 如果您想要无符号类型 uint least64 t 这些都定
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • C++ 标准是否指定了编译器的 STL 实现细节?

    在写答案时this https stackoverflow com questions 30909296 can you put a pimpl class inside a vector我遇到了一个有趣的情况 这个问题演示了这样一种情况

随机推荐

  • Rxjs 操作符实践指南

    操作符实战 1 工具方法型 count 统计总数 import range from rxjs import count from rxjs operators const numbers range 1 7 const result nu
  • python中16mod7_mod_python模块安装

    两 mod python 1 性能 使用mod python的主要优势在于比传统CGI更高的性能 一个測试 使用在Pentium 1 2GHz的机器上执行Red Hat Linux 7 3 使用4种类型的脚本 基于标准的CGI导入模块 以典
  • Android Glide加载图片圆角效果与ImageView的ScaleType冲突问题

    在imageVIew显示图片的时候一般是使用 android scaleType centerCrop 来让图片不被变形显示 但是如果现在用Glide来加载图片并给它转化出一个圆角 transform new GlideRoundTrans
  • 【导航】ESP32-C3 入门教程目录 【快速跳转】

    本文是 矜辰所致 的ESP32 C3 专栏的内容导航 结合自己的学习应用过程的总结记录 ESP32 C3入门教程 前言 一 环境篇 二 硬件篇 三 基础篇 四 Wi Fi篇 五 蓝牙篇 六 应用篇 前言 本系列教程以实际应用为目的 能够使得
  • 代码随想录 - Day37 - 贪心算法

    代码随想录 Day37 贪心算法 376 摆动序列 排除只有一个数的情况 把差值全部求出来放到dif里 在此过程中顺便去掉差值为0的情况 如果dif为空 说明里面所有差值为0 那么最长摆动序列只能是1 直接返回 如果dif不为空 把dif
  • OpenCV学习笔记——《基于OpenCV的数字图像处理》

    源码下载 下载资源包 bookln cn 常用函数库 英文 OpenCV OpenCV modules 中文 Welcome to opencv documentation OpenCV 2 3 2 documentation jetson
  • esp8266-01s介绍与使用

    esp826601s 是个比较常用的wifi模块 体积小 功能强大 说是可以用于工业 下面介绍esp826601s 可用引脚 以及可用功能 esp 01 ESP 01S 在ESP 01的基础上 优化了PCB天线 进行了一小步的升级 带来了一
  • label smooth的pytorch实现以及其公式推导(虽然短但是细)

    标签平滑 label smooth 标签平滑是一种正则化手段 目的为了解决onehot编码的缺陷 减少过拟合问题 在各种竞赛中广泛使用 涨点神器 假设 预测的结果为 y p r e d
  • elasticsearch常用命令

    curl X REST风格的语法谓词 节点ip 节点端口号 默认9200 索引名 索引类型 操作对象的ID号 curl localhost 9200 cat cat allocation cat shards cat shards inde
  • 小甲鱼零基础入门学习python笔记

    小甲鱼老师零基础入门学习Python全套资料百度云 包括小甲鱼零基础入门学习Python全套视频 全套源码 全套PPT课件 全套课后题及Python常用工具包链接 电子书籍等 请往我的资源 https download csdn net d
  • 说说看板在项目中的应用

    1 关于项目 1 1 概述 在任何组织中 项目其实就是一件需要大家共同努力配合完成的事情 且最后生产出的事物 是可以供他人长期使用的 好比一个蚁群 有蚁后 也有默默无闻的蚁兵们 蚁后负责命令大家搬食物 先搬这块再搬那块 蚁兵负责搬 大家排成
  • 延时消息队列

    目录 前言 一 延时队列实用场景 二 DelayQueue DelayQueue的实现 使用延迟队列 DelayQueue实现延时任务的优缺点 三 RocketMQ 原理 四 Kafka 原理 实现 DelayMessage定义 消息发送代
  • Linux中通过镜像搭建yum源

    上传镜像 首先 我们要给做实验的快照虚拟机配置好网络 并重启网络服务 当虚拟机的硬盘空间足够时 可以将真机的镜像文件直接发到虚拟机中 新建挂载目录进行挂载 我们将真机中的镜像发到快照中 但是提示空间不足 所以我们需要先删除发送到虚拟机上的镜
  • 非极大值抑制 nms

    非极大值抑制 Non max suppression 非极大值抑制 简称为NMS算法 英文为Non Maximum Suppression 其思想是搜素局部最大值 抑制极大值 非极大值抑制 在计算机视觉任务中得到了广泛的应用 例如边缘检测
  • Java_集合之Stack类的使用

    一 认识Stack 顾名思义 Stack代表是栈 栈是一种常用的数据结构 只能栈头插入元素 也只能从栈头出栈 遵循先进后出原则 栈好比手枪上弹夹的过程 最开始上的子弹会被压在最下面 最晚上的子弹反而最先被打出去 二 Stack类的使用 我们
  • 刷脸支付选择有软件开发能力的公司

    目前来说支付方式再次发生改变 从原本的扫码支付开始向着刷脸支付转变 因此作为企业商家就需要委托刷脸付款设备公司来定制优秀的刷脸支付系统和设备 而今天就请掌优技说说该如何选择这类公司 现在有很多刷脸付款设备公司 企业商家在选择时应该先把技术和
  • bwlabel函数的C语言实现及用法解析

    bwlabel函数的C语言实现及用法解析 在图像处理的领域中 连通区域标记是一项非常重要的技术 在C语言中 我们可以使用bwlabel函数来实现这个功能 本文将介绍bwlabel函数的实现原理和用法 并通过示例代码来演示其功能 bwlabe
  • 虚函数表详解

    一 概述 为了实现C 的多态 C 使用了一种动态绑定的技术 这个技术的核心是虚函数表 下文简称虚表 本文介绍虚函数表是如何实现动态绑定的 二 类的虚表 每个包含了虚函数的类都包含一个虚表 我们知道 当一个类 A 继承另一个类 B 时 类A会
  • 机器视觉软件工程师的生活是怎样的?

    大家好 本人是刚刚入职的视觉工程师 现在已经一年了 也给大家分享一下在这一段时间里 我做了什么 以及学到了什么 对了 虽然我只做了两个月的视觉工程师 但是我已经连续写了12年的日记了 我想把这个好习惯一直延续下去 也算是做个记录 可能没有人
  • c++多态及虚函数表内部原理实战详解

    1 多态实现方式 c 的多态机制主要是靠虚函数来实现 具体来说 就是用父类的指针指向子类的实例 然后通过父类指针调用子类对象中的成员函数 这样 就实现了父类指针的 多态 想了解虚函数实现机制 就必须先了解对象的存储方式 2 类的存储方式 我