C++逆向分析----虚函数与多层继承
虚函数
C++通过关键字virtual来将函数声明为一个虚函数。当一个类包含虚函数后编译器就会给类的实例对象增加一个虚表指针,所谓的虚表指针就是指向虚表的指针。虚表就是一张地址表,它包含了这个类中所有虚函数的地址。对象所在内存的前四个字节就是虚表指针。
class Test
{
public:
int iNum = 1;
virtual void Proc1();
virtual void Proc2();
virtual void Proc3();
};
Test test();
例如以上示例中,如果实例化一个Test对象,在内存中查看对象内存地址。因为前四个字节存储的是虚表指针,我们查看对应的虚表地址发现对应的三个虚拟函数的地址。


对象在刚实例化时虚表指针中的值是无意义的,当对象调用构造函数时,构造函数会对虚表指针进行初始化为当前类的虚表地址。

所以当我们没有为类提供默认的构造函数时,为了能够初始化虚表指针编译器会为类提供一个默认的构造函数,从而在构造函数中对虚表指针进行初始化。
虚函数的直接调用
test.Proc1();
当我们没有使用对象指针或者对象引用类调用虚函数时,例如通过类对象调用虚函数时,编译器并不会去查虚表,而是直接调用对应类的虚函数。(因为查表是无意义的,此时其虚表中的函数地址一定是等于实际的虚函数地址的)

虚函数的间接调用
Test* test;
test = new Test;
test->Proc1();
test->Proc3();
test->Proc2();
当我们利用指针或者是引用来调用虚函数时,编译器就会通过虚表指针查询虚表来调用虚表中对应的虚函数的地址,继而形成多态。

多层继承中的虚函数
class Test
{
public:
Test()
{
Proc1();
}
~Test()
{
Proc1();
}
virtual void Proc1();
virtual void Proc2();
virtual void Proc3();
};
class Test1:public Test
{
public:
virtual void Proc1();
};
Test1 test1;
上例码从Test类派生出Test1类,并在Test1类中覆盖虚函数Proc1,Test类的构造函数和析构函数中调用虚函数Proc1。
实例化一个Test1类对象,Test1的构造函数会先调用基类的Test类的构造函数。注意其传递的this指针指向的是Test1类对象。

Test类的构造函数会利用传递过来的this指针来初始化虚表指针指向自己的虚表,但注意实际此虚表指针是Test1类对象的。这样我们在Test类中调用的虚函数Proc1()就失去了应该有的多态性,编译器直接将虚函数调用变为直接调用方式。

继续调用完Test类的构造函数后回到Test1类的构造函数中,其会将虚表指针指向自己的虚表。这样就可以让指针调用虚函数时能够通过查虚表实现多态。

接着当test1对象生命周期结束时,其会先调用Test1类的析构函数。然后在调用Test类的析构函数。

Test类的析构函数会现将虚表指针在次修改指向Test类的虚表,然后我们调用虚函数Proc1就失去了多态性,编译器直接将虚函数调用变为直接调用方式。

以上虚表指针变化过程如下

那么为什么在Test1的析构函数中为什么不更改虚表指针呢?个人觉得是因为其更改后虚拟指针不会变化所以编译器将其优化没了。
至此我们可以得知类的构造函数会对虚表指针初始化,而析构函数会将虚表指针还原。且构造函数和析构函数中调用虚函数会失去多态性,全部变为直接调用方式。那么为什么在析构函数中不直接把所有的虚函数调用直接变为直接调用,还要多此一举将修改虚表指针呢(在调用父类Test的构造函数时将虚表指针指向Test类自己的虚表,在调用子类Test的析构函数时将虚表指针还原为指向Test类自己的虚表)。 原因是有可能在Test的 构造函数/析构函数 时会调用一个成员函数,而这个成员函数有可能会调用虚函数,间接调用形成多态。如果不还原虚拟指针的话,因为此时如果虚表指针指向Test1的虚表,此成员函数调用的虚函数就有可能是Test1类的成员函数。

C++逆向分析----虚函数与多层继承的更多相关文章
- c++内存分布之虚函数(单一继承)
系列 c++内存分布之虚函数(单一继承) [本文] c++内存分布之虚函数(多继承) 结论 1.虚函数表指针 和 虚函数表 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关.多一个父类,派生 ...
- c++内存分布之虚函数(多继承)
系列 c++内存分布之虚函数(单一继承) c++内存分布之虚函数(多继承) [本文] 结论 1.虚函数表指针 和 虚函数表 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关.多一个父类,派生 ...
- Visual C++ 8.0对象布局的奥秘:虚函数、多继承、虚拟继承(VC直接输出内存布局)
原文:VC8_Object_Layout_Secret.html 哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleCl ...
- VC++对象布局的奥秘:虚函数、多继承、虚拟继承
哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout ...
- 虚函数重载(overwrite) 继承覆盖问题
引言 类接口需要添加默认参数,以适应不同情况调用, 但是clang-tidy 不允许在接口上设置默认参数,ps: 可能担心继承类里接口重新设置新默认参数而导致误用的情况 #include <st ...
- C++虚函数与多继承
虚函数 C++用虚函数实现运行时多态,虚函数的实现是由两个部分组成的,虚函数指针与虚函数表. 虚函数指针(vptr)是指向虚函数表的指针,在一个被实例化的对象中,它总是被存放在该对象的地址首位.而虚函 ...
- C++中的继承与虚函数各种概念
虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...
- C++ 派生类函数重载与虚函数继承详解
目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...
- C++反汇编与逆向分析技术揭秘
C++反汇编-继承和多重继承 学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...
随机推荐
- Python-jet后台管理的使用
python-django-jet库的使用 1.安装 pip install django-jet 2.配置 将'jet'应用添加到你的Django项目的设置文件settings.py中的INSTAL ...
- CF524F And Yet Another Bracket Sequence 题解
题目链接 算法:后缀数组+ST表+贪心 各路题解都没怎么看懂,只会常数巨大的后缀数组+ST表,最大点用时 \(4s\), 刚好可以过... 确定合法序列长度 首先一个括号序列是合法的必须满足以 ...
- 如何获取占用U盘的进程
依次打开开始---所有程序---附件---系统工具---资源监视器. 打开CPU标签栏,在"关联的句柄"中的搜索框中输入U盘的盘符,如G: 按回车搜索即可出结果. 在搜索结果中右键 ...
- kubernetes 降本增效标准指南| 容器化计算资源利用率现象剖析
作者:詹雪娇,腾讯云容器产品经理,目前主要负责腾讯云集群运维中心的产品工作. 张鹏,腾讯云容器产品工程师,拥有多年云原生项目开发落地经验.目前主要负责腾讯云TKE集群和运维中心开发工作. 引言 降本增 ...
- Android Studio 之 RadioButton
•任务 如何通过 RadioButton 实现如图所示的界面? •基本用法 RadioButton 单选按钮,就是只能够选中一个,所以我们需要把 RadioButton 放到 RadioGroup 按 ...
- Java 读取Word文本/段落格式属性
本文介绍通过Java后端程序代码来读取Word文本和段落格式的方法. 本次测试环境如下: Word版本:2013 编译环境:IntelliJ IDEA2018 Work库:free spire.doc ...
- Web 前端 - 浅谈外部手动控制 Promise 状态
前言 当有多个共享资源.协同操作的时候,往往需要根据动态亦或是复杂的条件以控制和调用程序逻辑. 还是那句话,懂的人自然懂,不懂的人也搜不到这个随笔. 设计 PendingPromise<T> ...
- [BFS]最小转弯问题
最小转弯问题 Description 给出一张地图,这张地图被分为 n×m(n,m<=100)个方块,任何一个方块不是平地就是高山.平地可以通过,高山则不能.现在你处在地图的(x1,y1)这块平 ...
- OO_Unit 3 JML规格化设计总结
OO_Unit 3 JML规格化设计总结 JML语言概述(Level 0) 概念定义 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML ...
- libnet的使用详解
最近搬砖需要对libnet进行介绍在这里对知识进行汇总. 1.libnet简介 在libnet出现以前,如果要构造数据包并发送到网络中,程序员要通过一些复杂的接口来处理.libnet的出现,为程序员提 ...