虚函数

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++逆向分析----虚函数与多层继承的更多相关文章

  1. c++内存分布之虚函数(单一继承)

    系列 c++内存分布之虚函数(单一继承) [本文] c++内存分布之虚函数(多继承) 结论 1.虚函数表指针 和 虚函数表 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关.多一个父类,派生 ...

  2. c++内存分布之虚函数(多继承)

    系列 c++内存分布之虚函数(单一继承) c++内存分布之虚函数(多继承) [本文] 结论 1.虚函数表指针 和 虚函数表 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关.多一个父类,派生 ...

  3. Visual C++ 8.0对象布局的奥秘:虚函数、多继承、虚拟继承(VC直接输出内存布局)

    原文:VC8_Object_Layout_Secret.html 哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleCl ...

  4. VC++对象布局的奥秘:虚函数、多继承、虚拟继承

    哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout ...

  5. 虚函数重载(overwrite) 继承覆盖问题

    引言 类接口需要添加默认参数,以适应不同情况调用, 但是clang-tidy 不允许在接口上设置默认参数,ps: 可能担心继承类里接口重新设置新默认参数而导致误用的情况 #include <st ...

  6. C++虚函数与多继承

    虚函数 C++用虚函数实现运行时多态,虚函数的实现是由两个部分组成的,虚函数指针与虚函数表. 虚函数指针(vptr)是指向虚函数表的指针,在一个被实例化的对象中,它总是被存放在该对象的地址首位.而虚函 ...

  7. C++中的继承与虚函数各种概念

    虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...

  8. C++ 派生类函数重载与虚函数继承详解

    目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...

  9. C++反汇编与逆向分析技术揭秘

    C++反汇编-继承和多重继承   学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...

随机推荐

  1. python使用try...except语句处理异常

    try....except语句语法格式: try: <语句> except(异常名称): <语句> 注意在except语句中的括号中的异常名称是可以省略的,当省略时就是全捕捉 ...

  2. 吃透 MQ

    本文主要讲解 MQ 的通用知识,让大家先弄明白:如果让你来设计一个 MQ,该如何下手?需要考虑哪些问题?又有哪些技术挑战? 有了这个基础后,我相信后面几篇文章再讲 Kafka 和 RocketMQ 这 ...

  3. python2 虚拟环境

    1.安装virtualenv pip install virtualenv 2.创建虚拟环境 virtualenv 虚拟环境名称  3.激活虚拟环境 source 虚拟环境目录/bin/activat ...

  4. Spring Boot 轻量替代框架 Solon 1.3.18 发布

    Solon 是一个微型的Java开发框架.项目从2018年启动以来,参考过大量前人作品:历时两年,4000多次的commit:内核保持0.1m的身材,超高的跑分,良好的使用体验.支持:RPC.REST ...

  5. 2020 OO 第二单元总结

    只要跑得够快即使从头关到尾你也喜欢吗? 一.设计策略 1.1 总体策略概述 在多线程的协同和同步控制方面,我三次作业都是采用生产者/消费者模式(还憨憨地在内部分了customer.producer.t ...

  6. BUAA_OO_2020_第二单元总结

    BUAA_OO_2020_第二单元总结 第一次 设计策略 本次作业采用生产者.消费者模式设计,大致框架如图所示: 生产者:输入线程 消费者:电梯线程 托盘:Dispatcher调度器 线程安全方面,调 ...

  7. SpringBoot+Dubbo+Zookeeper 实例

    前言 当下Java 生态环境里面,微服务占据了非常大的份额,现在大部分新开发的 Java选型的后台程序都很奇妙的会跟微服务发生一些关系.那目前市面上主流的微服务方向主要有 Spring 家族推出的Sp ...

  8. IDEA通过Maven打包JavaFX工程(OpenJFX11)

    1 概述 最近研究JFX,写出来了但是打包不了,这...尴尬... IDEA的文档说只支持Java8打成jar包: 尝试过直接使用Maven插件的package,不行,也尝试过Build Artifa ...

  9. JAVAEE_Servlet_03_Servlet对象生命周期

    Servlet的对象声明周期 * 什么是对象生命周期? - 生命周期表示一个JAVA对象从创建到销毁的过程是一个生命周期 * Servlet对象生命周期 1. Servlet对象创建 无参构造 2. ...

  10. aws eks上部署 ingress-nginx 加NLB

    转载自https://kubernetes.github.io/ingress-nginx/deploy/#aws In AWS we use a Network load balancer (NLB ...