浅析虚函数的vptr和虚函数表

浅析虚函数的vptr和虚函数表

文章目录

  • 浅析虚函数的vptr和虚函数表
    • 前言
    • 1. 基础理论
    • 2. 实现与内部结构

前言

​ 为了实现虚函数,C++使用一种称为虚拟表的特殊形式的后期绑定。该虚拟表是用于解决在动态/后期绑定方式的函数调用函数的查找表。虚拟表有时会使用其他名称,例如 “vtable”,“虚函数表”,“虚方法表” 或 “调度表”。本文就实际案例出发,对虚表指针和虚函数表的模型进行刻画。


1. 基础理论

​ 首先,每个使用虚函数的类(或者从使用虚函数的类派生)都有自己的虚拟表。该表只是编译器在编译时设置的静态数组。虚拟表包含可由类的对象调用的每个虚函数的一个条目。此表中的每个条目只是一个函数指针,指向该类可访问的派生函数。

​ 其次,编译器还会添加一个隐藏指向基类的指针,我们称之为 vptr。vptr在创建类实例时自动设置,以便指向该类的虚拟表。与this指针不同,this指针实际上是编译器用来解析自引用的函数参数,vptr是一个真正的指针

​ 因此,vptr使每个类对象的分配大一个指针的大小。这也意味着vptr由派生类继承,这很重要。

2. 实现与内部结构

下面我们来看自动与手动操纵vptr来获取地址与调用虚函数!

开始看代码之前,为了方便理解,这里给出调用图:

在这里插入图片描述

#include <iostream>using namespace std;/*** @brief 函数指针*/
typedef void (*Fun)();/*** @brief 基类*/
class Base
{
public:Base() {};virtual void fun1() { cout << "Base::fun1()" << endl; }virtual void fun2() { cout << "Base::fun2()" << endl; }virtual void fun3() {}    // 注意虚函数fun3()未被子类重写~Base() {};
};/*** @brief 派生类*/
class Derived : public Base
{
public:Derived() {};void fun1() { cout << "Derived::fun1()" << endl; }void fun2() { cout << "DerivedClass::fun2()" << endl; }~Derived() {};
};/*** @brief* 获取vptr地址与func地址,vptr指向的是一块内存,这块内存存放的是虚函数地址,这块内存就是我们所说的虚表** @param obj* @param offset** @return*/
Fun getAddr(void* obj, unsigned int offset)
{cout << "=======================" << endl;void* vptr_addr =(void*)*(unsigned long*)obj; // 64位操作系统,占8字节,通过*(unsigned// long *)obj取出前8字节,即vptr指针printf("vptr_addr:%p\n", vptr_addr);/** @brief 通过vptr指针访问virtual* table,因为虚表中每个元素(虚函数指针)在64位编译器下是8个字节,因此通过*(unsigned* long *)vptr_addr取出前8字节, 后面加上偏移量就是每个函数的地址!*/void* func_addr = (void*)*((unsigned long*)vptr_addr + offset);printf("func_addr:%p\n", func_addr);return (Fun)func_addr;
}int main(void)
{Base ptr;Derived d;Base* pt = new Derived(); // 基类指针指向派生类实例Base& pp = ptr; // 基类引用指向基类实例Base& p = d; // 基类引用指向派生类实例cout << "基类对象直接调用" << endl;ptr.fun1();cout << "基类引用指向基类实例" << endl;pp.fun1();cout << "基类指针指向派生类实例并调用虚函数" << endl;pt->fun1();cout << "基类引用指向派生类实例并调用虚函数" << endl;p.fun1();// 手动查找vptr 和 vtableFun f1 = getAddr(pt, 0);(*f1)();Fun f2 = getAddr(pt, 1);(*f2)();delete pt;return 0;
}

运行结果:

基类对象直接调用
Base::fun1()
基类引用指向基类实例
Base::fun1()
基类指针指向派生类实例并调用虚函数
Derived::fun1()
基类引用指向派生类实例并调用虚函数
Derived::fun1()
=======================
vptr_addr:00EBAB68
func_addr:00EB1217
Derived::fun1()
=======================
vptr_addr:00EBAB68
func_addr:00EB126C
DerivedClass::fun2()

对应的打开反汇编界面,查询Derived::fun1()DerivedClass::fun2()的地址:

在这里插入图片描述

通过对比观察发现,我们通过程序读取到的虚函数调用地址与汇编中定义的类内虚函数地址一致!

注:这段代码在x86环境下正常运行如上,但x64会访问越界报错,可能是我这边的问题,恳请点拨!

在这里插入图片描述

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

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

相关文章

图像处理ASIC设计方法 笔记6 数据拼接和帧格式校正

第四章大模板卷积ASIC设计方案 P80 实时图SPRM 数据位宽64bit&#xff0c;4个SPRAM&#xff0c;同时得到4行数据 绘制卷积芯片数据路径图&#xff0c;卷积芯片内部模块图 根据这个图&#xff0c;本书后续对各个模块都进行介绍。 P81 第一个模块 图像输入前端FIFO 学习图像处…

关于脉冲负载应用中电阻器,您需要了解的 11 件事?

不幸的是&#xff0c;电阻器在脉冲负载下可能会失效。当脉冲功率耗散到器件的电阻元件时&#xff0c;它会产生热量并增加电阻器的温度。过热会损坏电阻元件&#xff0c;导致电阻变化甚至设备开路。为了避免在设计中出现这种情况&#xff0c;以下是您在选择元件时应了解的有关电…

RocketMQ - 深入研究一下Broker是如何持久化存储消息的

1. CommitLog消息顺序写入机制 首先思考一下,当生产者的消息发送到一个Broker上的时候,他接收到了一条消息,接着他会对这个消息做什么事情? 首先第一步,他会把整个消息直接写入磁盘上的一个日志文件,叫做CommitLog,直接顺序写入这个文件,如下图: 这个CommitLog是很…

redis运维

1.备份redis配置文件 cp /etc/redis.conf /etc/redis.conf.bak 2.将redis中不要的注释和空行删除 sed -i /^#/d; /^$/d /etc/redis.conf 3.redis配置文件 bing 0.0.0.0 &#xff1a;绑定本机所有网卡 daemonize yes&#xff1a;设置后台运行 requirepass redispwd…

二维码门楼牌管理系统技术服务:构建智慧城市新标准

文章目录 前言一、二维码门楼牌管理系统的诞生背景二、标准地址编码的定义与作用三、二维码门楼牌管理系统的核心技术四、二维码门楼牌管理系统的应用优势五、二维码门楼牌管理系统在智慧城市建设中的作用六、结论与展望 前言 随着城市化的快速发展&#xff0c;传统的门楼牌管…

linux安装matlab获取许可证

1.点击许可证 2. 3. 4. 4.主机ID 打开linux输入 /sbin/ifconfigether后边的就是 6.计算机登录名 打开linux输入 whoami7. 8. 9.

第十六天-爬虫selenium库

目录 1.介绍 2.使用 selenium 1.安装 2.使用 1.测试打开网页&#xff0c;抓取雷速体育日职乙信息 2.通过xpath查找 3.输入文本框内容 send_keys 4.点击事件 click 5.获取网页源码&#xff1a; 6.获取cookies 7.seleniumt提供元素定位方式&#xff1a;8种 8.控制浏览…

Linux/Docker 修改系统时区

目录 1. Linux 系统1.1 通过 timedatectl 命令操作1.2 直接修改 /etc/localtime 文件 2. Docker 容器中的 Linux 操作环境&#xff1a; CentOS / AlmaOSMySQL Docker 镜像 1. Linux 系统 1.1 通过 timedatectl 命令操作 使用 timedatectl list-timezones 命令列出可用的时区…

4. 编写app组件

1. 代码 main.ts // 引入createApp用于创建应用 import {createApp} from "vue"// 引入App根组件 import App from ./App.vue createApp(App).mount(#app) App.vue <!-- vue文件可以写三种标签1. template标签&#xff0c;写html结构2. script 脚本标签&…

外包干了6个月,技术退步明显

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

HTML+CSS+JS:日夜交替

效果演示 实现了一个简单的日夜交替效果的动画。页面中包含了太阳、月亮和海洋的元素&#xff0c;通过切换按钮可以切换页面的主题&#xff0c;从白天切换到黑夜&#xff0c;或者从黑夜切换到白天。 Code <div class"btn-box"><div class"sunBtn"…

unity学习(45)——选择角色菜单——客户端处理服务器的数据

1.已知客户端ReceiveCallBack中已经收到来自服务器返回的数据包。 2.问题是客户端MessageManager中的Update并没有拆解该数据包 &#xff0c;因该是因为脚本没有挂载。 挂在SelectMenu场景中的Camera上即可。 挂载后成功达到目地 其中Update中的List是一个起到全局效果的static…