-
-
[原创]从反汇编的角度学C/C++之虚函数,单继承与多态
-
发表于: 2021-10-2 20:14 6909
-
虚函数最为C++可以说是最重要的一个特征被广泛应用,那么它的内部机制究竟如何,它和普通函数使用的区别在哪里。定义如下的类
以上类定义中,有3个虚函数和1个普通的成员函数,我们首先看看增加了虚函数以后类变量是否有所变量以及对构造函数是否有影响
由以上反汇编代码可以看出对于有虚函数的类变量,在初始化类成员之前会首先赋给一个地址,这个地址其实就是虚函数表的地址。这个地址在类变量的偏移为0的地方,随后的类成员依次向下排布。此时查看类变量地址对应的内存如下
可以看到对于有虚函数的类变量地址处首先存储的是我们的虚函数表,然后才是我们的类成员,至于这个虚函数表的内容是啥,为了方便展示这边使用IDA查看,如下图所示
可以看到这个地址对应了3个4地址,而这3个地址其实就是我们所写的三个虚函数。那么对于虚函数的调用与普通的成员函数有什么区别
由上可以看出如果是对象对虚函数或者是对象指针对普通函数的调用都是直接call函数地址,而如果是对象指针对虚函数调用,程序首先会去虚函数表中找到对应的虚函数地址,在对它进行call。
继承作为面向对象的核心概念被程序员广泛使用,那么对象的继承在内存中是如何表现得,修改上述得类定义如下
根据如上得定义,类Sub继承Base,首先看看继承得类构造函数的变化如下
可以看到,对于子类,首先会调用父类的构造函数,而子类的成员地址,也会往后排列,上述父类构造函数的内容如下:
由上可以得出结果,在子类构造函数中,首先会调用父类的构造函数对类变量地址空间中的变量进行赋值,其次才是本类的成员赋值。所以继承的子类中的成员变量的排列是在父类成员变量后面,在内存中查看子类变量的地址得到如下结果
这里有一个概念是继承中可以选择public得公有继承和private得继承继承,区别就是private得继承会把父类得成员变量和函数变成私有的在内存中保存,但是这只是编译器的检查,根据之前发的文章可以知道我们可以很容易的用指针的方式访问这些私有成员。
接下来看看虚函数在继承的子类中是如何保存的,修改上述的定义如下
首先我们依然先查看构造函数的调用情况
唯一的变化就是在调用完父类构造函数后,程序首先对类变量地址为0的地方赋值了一个虚函数表的地址,接下来看看父类构造函数的内容
可以看到父类构造函数的内容和最开始讲解虚函数时候的内容是一样的,此时在用IDA查看两张虚函数表的内容如下
虚函数表里面存着的是各个虚函数的地址,经过查看以后对着写函数进行重命名如下
可以看到虚函数Base_fun2由于子类没用重载他,所以也在子类的虚函数表里面,而fun1, fun3由于被覆盖了,所以虚函数表存储的是Sub_fun1和Sub_fun3。由此可以得出结论,父类的虚函数会如果没有被子类重载,那么子类的虚函数表中就会有父类的虚函数,如果被重载了,虚函数表中就会存储子类的虚函数。
根据上面的内容可以总结出以下三点结论
当用指针调用虚函数的时候,程序会从虚函数表中找到相应的函数进行调用。
子类调用构造函数的时候,先调用父类的构造函数,父类的构造函数会先用自己的虚函数表覆盖在最开始的类变量地址中,接着子类在将自己的虚函数表覆盖在开始的类变量地址中,然后在对变量成员进行初始化。
如果子类中有对父类的虚函数进行重载,那么子类的虚函数表中存储的这个虚函数就是子类重载后的虚函数。
跟据上面的三点结论,就应该可以很好理解多态这一概念了,修改类定义如下
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]CVE-2022-21882提权漏洞学习笔记 16383
- [原创]CVE-2021-1732提权漏洞学习笔记 19489
- [原创]CVE-2014-1767提权漏洞学习笔记 15192
- [原创]CVE-2018-8453提权漏洞学习笔记 18526
- [原创]CVE-2020-1054提权漏洞学习笔记 13542