C++迈向精通:当我尝试修改虚函数表
尝试修改虚函数表
本期纯整活儿好吧!!!!
初衷
有一天我突然开始好奇虚函数表是否真的存在,于是我开始想是否能够从C++中查看或者调用虚函数表中的内容。,于是有了下面的操作。
操作过程
起初我并没有思路,但是我知道,每一个类对应一个虚函数表,因此首先我需要一个虚函数,因此我随便写了一个基类:
class Base {
public:void output() {cout << "Class Base" << endl; };virtual void say() {cout << "Class Base" << endl;}
};
然后写一个子类,去 override 一下他的这个函数:
class A : public Base {
public:void output() {cout << "Class A" << endl;}void say() override {cout << "Class A" << endl;}int x;
};
然后按照同样的方式再创建一个 B 类:
class B : public Base {
public:void output() {cout << "Class B" << endl;}void say() override {cout << "Class B" << endl;}
};
这样以来,应该会有三个虚函数表,分别是:
- Base基类对应的虚函数表
- A类对应的虚函数表
- B类对应的虚函数表
然后如何调用他们呢?我想了好久,想出这样的一个方法:
int main() {A a;B b;cout << "A's virtual table address : " << ((void **)(&a))[0] << endl;cout << "A's virtual table address : " << ((void **)(&b))[0] << endl;return 0;
}
根据理论来说,C++中的虚函数表应该在类内空间的第一个位置,占八个字节,是一个指向函数表的指针,那么我们就应该这样做:
((void **)(&b))[0];
这会返回一个虚函数表的地址。
这句话是什么意思呢?首先我们要清楚,对象的空间分配与结构体是一样的,而根据理论来看,虚函数表的指针会被编译器自动添加在对象空间的初始位置,也就是说,对象所在的空间的第一个单元存储的是虚函数表的地址。
如何获得这个首地址呢?首先我们要像取数组首地址一样,用取地址符号获得对象的首地址。然后将其强制转换为 (void **) 类型,这相当于让电脑将这个对象的空间看作一个数组,这个数组中存放的全部都是指向 void * 类型的数据的地址。
void * 类型是函数指针类型,我们不用管,最后在末尾添加[0]就相当于得到了虚函数表的地址。
尝试输出一下:

嗯,看起来没啥问题,但是如何证明他是个虚函数表的地址呢?
我能否将一个类中的修改到另一个虚函数表中?然后让这个对象执行的时候出现另外一个类的动作?
于是我开始了下面的尝试:
int main() {A a;B b;cout << "Class A virtual table address : " << ((void **)(&a))[0] << endl;cout << "Class B virtual table address : " << ((void **)(&b))[0] << endl;((void **)(&a))[0] = ((void **)(&b))[0]; // 把b对应的类的虚函数表覆盖到a上a.say(); // 如果虚函数表被覆盖了的话,那么就会出现a执行了b的say方法的状况b.say();return 0;
}
然而结果是这样的:

发现结果并没有被改变,这是怎么回事?我百思不得其解,多方询问过之后了解到是gcc编译器把我的虚函数的调用过程给优化掉了,无奈我只能使用指针和引用来赋值:
int main() {A a;B b;Base *ap = &a, *bp = &b;cout << "Class A virtual table address : " << ((void **)(&a))[0] << endl;cout << "Class B virtual table address : " << ((void **)(&b))[0] << endl;((void **)(&a))[0] = ((void **)(&b))[0]; // 把b对应的类的虚函数表覆盖到a上ap->say(); // 如果虚函数表被覆盖了的话,那么就会出现a执行了b的say方法的状况bp->say();return 0;
}

执行成功啦!!!
其实后面我还做了很多好玩的操作,这里先不放出来,写的有点累,下次再凑出一篇来!
:wq 拜拜~~
相关文章:
C++迈向精通:当我尝试修改虚函数表
尝试修改虚函数表 本期纯整活儿好吧!!!! 初衷 有一天我突然开始好奇虚函数表是否真的存在,于是我开始想是否能够从C中查看或者调用虚函数表中的内容。,于是有了下面的操作。 操作过程 起初我并没有思路…...
IDEA 高效插件工具
文章目录 LombokMaven Helper 依赖冲突any-rule(正则表达式插件)快速生成javadocGsonFormat (Aits) 将json解析成类Diagrams使用 类图SequenceDiagram时序图GenerateAllSetter(AltEnter)大小写转写String ManipulationGitToolBox 代码提交人activate-pow…...
SQL入门大全
SQL(Structured Query Language,结构化查询语言)是一种用于管理关系型数据库的标准编程语言。它具有数据操纵和数据定义等多种功能,为数据库管理系统提供了强大的交互性特点,能够极大地提高计算机应用系统的工作质量与…...
【深度优先搜索 广度优先搜索】297. 二叉树的序列化与反序列化
本文涉及知识点 深度优先搜索 广度优先搜索 深度优先搜索汇总 图论知识汇总 LeetCode297. 二叉树的序列化与反序列化 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传…...
App UI 风格,引领设计风向
App UI 风格,引领设计风向...
TIM—通用定时器高级定时器
通用/高级定时器的功能 在基本定时器功能的基础上新增功能: 通用定时器有4个独立通道,且每个通道都可以用于下面功能。 (1)输入捕获:测量输入信号的周期和占空比等。 (2)输出比较:产…...
【数据结构与算法(C语言)】循环队列图解
目录 1. 前言1.1 普通循环队列假溢出1.1.1 初始化队列1.1.2 插满队列1.1.3 删除元素后,再插入元素 1.2 循环队列1.2.1 插入元素,队列已满1.2.2 将元素J1、J2出列,循环队列又空出两个空间1.2.3 元素J6可以继续入列 2. 存储结构和函数说明2.1 队…...
私域流量转化不济的原因
你是不是也曾感到私域流量的转化一直不如意?让我来告诉你,这六大问题是为什么,以及如何轻松解决它们,提升你的私域流量转化率! 1. 问题:目标不明确 你是否常常感到茫然,不知道私域流量应该有何目…...
百万上下文RAG,Agent还能这么玩
❝ 在AI技术飞速发展的今天,我们见证了许多令人惊叹的突破。最近,Qwen2模型的开源引起了广泛的关注,它不仅展示了超越闭源模型的能力,还带来了一个全新的框架——Qwen-Agent。 Qwen-Agent的设计思路虽然与LangChain相似࿰…...
【后端开发】服务开发场景之高可用(冗余设计,服务限流,降级熔断,超时重试,性能测试)
【后端开发】服务开发场景之高可用(冗余设计,服务限流,降级熔断,超时重试,性能测试) 文章目录 序:如何设计一个高可用的系统?可用性的判断指标是什么?哪些情况会导致系统…...
在 Selenium 中更改 User-Agent | 步骤与最佳实践
在 Selenium 中更改 User Agent 是许多网页抓取任务中的关键步骤。它有助于将自动化脚本伪装成常规浏览器,从而避免被网站检测到。本指南将带您了解如何在 Selenium 中更改 Google Chrome 的 User Agent,并提供最佳实践以确保您的网页抓取任务顺利进行。…...
2024酒店IPTV云桌面系统建设方案
Hello大家好,我是点量小芹,这一年多的时间一直在分享实时云渲染像素流相关的内容,今天和大家聊聊酒店IPTV云桌面电视系统解决方案,或者有的朋友也会称之为IPTV服务器。熟悉小芹的朋友知道,IPTV软件系统是我们一直在推的…...
java Thrift TThreadPoolServer 多个processor 的实现
当我们使用Thrift 通信的时候,服务端有时候需要注册多个类,去实现通信,这时候我们就不能再使用单一Processor的方式,就要使用多个Processor,那么如何去实现呢? 多个Process 服务端 public static void m…...
失眠焦虑的解脱之道:找回内心的平静
🍃 在这个快节奏的时代,失眠与焦虑似乎成了许多人的隐形敌人。每当夜幕降临,它们便悄悄潜入心底,扰乱我们的思绪,让宁静的夜晚变得无比漫长。然而,生活总有办法让我们找回内心的平静,只需稍作调…...
OLED柔性屏的显示效果如何
OLED柔性屏的显示效果非常出色,具有多方面的优势。以下是关于OLED柔性屏显示效果的详细分析: 色彩表现:OLED柔性屏的每个像素都可以独立发光,因此色彩准确性极高。黑色呈现得非常深邃,而亮部则展现出鲜明而生动的细节。…...
百货商城优选 伊利牛奶推出全国首款减甲烷环保学生奶
近日,伊利集团受邀参加在全国首个“国际首脑峰会零碳场馆”召开的“降碳增产科技助力奶业绿色高质量发展”首款低碳饲料创新大会。会上,伊利宣布将推出全国首款减甲烷环保学生牛奶——伊利QQ星学生纯牛奶,进一步将可持续发展落到实处…...
Fluid 1.0 版发布,打通云原生高效数据使用的“最后一公里”
作者:顾荣 前言 得益于云原生技术在资源成本集约、部署运维便捷、算力弹性灵活方面的优势,越来越多企业和开发者将数据密集型应用,特别是 AI 和大数据领域应用,运行于云原生环境中。然而,云原生计算与存储分离架构虽…...
软件测试--第十一章 设计和维护测试用例
1.单选题 (2分) 下面有关测试设计的叙述,说法不正确的是( )。 A 测试用例的设计是一项技术性强.智力密集型的活动 B 在开展测试用例设计前,必须将测试需求进行详细展开 C 在一般的测试组织内,测试用例的评审可能不是正式的评审会 D 在测试用例设计时,只设计覆盖正常流程和操…...
前端只允许一次函数调用
如果你正在进行前端开发,并且只想允许一次函数调用,你可以使用JavaScript的闭包结构创建一个只能被调用一次的函数。这样的函数有时被称为单次调用函数(“one-time call” functions)或一次性函数(“once” functions&…...
visdom使用时所遇的问题及解决方法
最近在用visdom进行可视化的过程中,虽然可有效的避免主机拒绝访问(该问题的解决方法,请参考深度学习可视化工具visdom使用-CSDN博客)即在终端输入python -m visom.server 1.训练过程中visdom出现ValueError: too many file descr…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
