C++反汇编第四讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.
目录:
1.多重继承在内存中的表现形式
多重继承在汇编中的表现形式
2.菱形继承
普通的菱形继承
虚继承
汇编中的表现形式
一丶多重继承在内存中的表现形式
高级代码:
class Father1
{
public:
Father1(){}//空构造
virtual ~Father1(){} //空析构
virtual void Player(){} //玩耍的函数
int m_price;//金钱
};
class Father2
{
public:
Father2(){}
virtual ~Father2(){}
virtual void SetMoney(){}//设置金钱
int m_Money;
};
class Child : public Father1, public Father2 //继承两个父类
{
public:
Child(){}
virtual ~Child(){}
virtual void Eat(){}//吃的函数 };
int main(int argc, char* argv[])
{
Child MyChild; //只需要注意这里,以及继承两个父类的地方
return ;
}
通过main函数我们得知,我们生成了一个孩子类的对象.此时按照C/C++的规范,应该先从左往右依次构造父类1,父类2
此时的情况和我们昨天所讲的单继承里面,包含一个成员是一样的.但是有不同之处
1.在子类自身构造中会复写两次虚表.
2.在父类2指向子类的时候,会产生三木目运算的表达式.
2.观看反汇编中的表现形式.
1.main函数下,构造位置处

2.自身构造函数内部

可以看出,自身构造中会产生如上代码,首先
1.先构造父类各自的虚表,相当于一开始父类1有父类1的虚表,父类2有父类2的虚表
2.然后自身构造的时候,分别对其自己的父类的虚表进行复写行为.通过这一点,可以简单的断定子类有两个父类.
Release下的反汇编

在Release下,因为我们的父类都是空的,所以直接优化了.
但是我们会发现有一个三木运算符的反汇编代码
neg eax
sbb eax,eax
lea ecx,[this] //获得this指针,简写了
and eax,ecx
这个是一个无分支三目运算的反汇编代码,在讲解C语言的反汇编的时候已经讲解过了,但为什么会出现这个.
首先
如果我们代码写成 father2 *p2 = &ch; 是没有问题的是把, 父类指针,指向了子类, 因为father2在内存中需要加便宜,所以直接加便偏移即可.
但是此时问题来了.如果我们写成
father2 *p2 = NULL;
p2 = &ch; 怎么办,此时还要不要加偏移了?
所以产生了一个三木运算的表达式
p2 ? NULL : NULL : p2 + offset; 在经过编译器的一优化,所以变成了上面的代码.
如果不懂father *p2 = &ch,那要从内存角度看了.首先看Debug下的反汇编.

我们的father2构造的位置是在内存+8的位置进行构造的,所以此时你如果father2的指针指向子类,那么需要+8对不对,所以如果先给NULL,就不用+8了.所以产生了三木运算符.
内存结构图

之后的内存结构图,最后子类自己的构造中需要复写两个父类自己的虚表.而且自己扩展的虚函数会放在父类1的虚表中.
总结:
1.识别双父类的时候,看自己的构造中是否进行了两次虚表复写行为,(多个父类则多次构造父类,多次复写父类虚表行为)
2.识别指针的三目运算,父类指针指向子类对象的时候,会产生三目运算表达式 例如: p2 = &child; p2有可能会有==NULL的情况下,如果不等于NULL,那么自己需要+offset进行指向,
所以产生了三目表达式, p2 ? NULL ; NULL ,p2 + offset;
3.析构中也会进行两个父类析构,然后重新填写虚表的行为.(和构造相反)
二丶菱形继承
1.普通的菱形继承讲解
普通的菱形继承,为什么说普通的.请看高级代码
高级代码:
class CGrandFather //新添加的爷爷类
{
public: CGrandFather(){}
virtual ~CGrandFather(){}
virtual void Dance(){}//随便写的虚函数
int m_int };
class Father1 : public CGrandFather
{
public:
Father1(){}//空构造
virtual ~Father1(){} //空析构
virtual void Player(){} //玩耍的函数
int m_price;//金钱
};
class Father2: public CGrandFather
{
public:
Father2(){}
virtual ~Father2(){}
virtual void SetMoney(){}//设置金钱
int m_Money;
};
class Child : public Father1, public Father2 //继承两个父类
{
public:
Child(){}
virtual ~Child(){}
virtual void Eat(){}//吃的函数 };
int main(int argc, char* argv[])
{
Child MyChild;
return ;
}
通过上面我们可以得出一个逻辑图:

单其实反汇编那种不是这样的.
从反汇编和内存中可以看出,每一个父类都有一个自己的爷爷类.而且每个父类构造爷爷类的时候,都会填写爷爷类的虚表,并且在自己的构造中对其复写(重写)
所以形成了下面这样的图

所以我们修改我们的高级代码.
int main(int argc, char* argv[])
{
Child MyChild;
//MyChild.m_int = 1; //重点是这句
MyChild.Father1::m_int = ;
return ;
}
我们调用爷爷类中的m_int的时候会出现错误,因为不明确,因为通过上图我们得出,每个父类都有自己的爷爷类,这时候你访问m_int,需要指明那个父类的,
而且你修改父类1的m_int不会影响父类2的m_int.造成了数据冗余的设计.
2.从反汇编的角度下看
1.main函数下构造的位置

2.自身构造内部

构造内部同样进行两次父类先构造的情况,最后复写两个父类的虚表
3.父类1的构造内部

父类1的构造内部又构造爷爷类,爷爷类在自身位置填写虚表,完了之后父类1又复写了.
父类2同上.
得出结论:
1.菱形继承的时候,会有三次改虚表的动作
构造爷爷类的时候修改
构造完爷爷类之后父类修改
构造完父类之后孩子类修改.
2.每个父类都会构造自己的父类.
Release下的反汇编表现形式

还是一样,Release下会有优化.指针+不加偏移产生的无分支三目运算.
三丶虚继承.
通过第普通的菱形继承,我们得出了每一个父类都会有一个父类(爷爷类)然后产生了相同的数据,且数据不明确必须指明调用,所以C++为了解决这种问题,出了一个虚继承.
直接贴上来内存结构:

有人说,为什么爷爷类会放在下面.而不是上面,视编译器而定,也可以放在上面.为什么放在上面说来话长,不符合此博客的篇幅.
提示一句:把自己当做编译器.
根据构造的时候先父类构造我们得出了.
首先爷爷类会先构造,但是此时有一个问题,我们要怎么知道爷爷类在哪里.所以这个时候就需要进行记录了.
然后我们上面的内存结构变为了下面这样.

每个父类记录一下爷爷类的偏移即可.这个偏移是编译器填写的.
新的问题:
我们怎么知道爷爷类是在下面还是在上面
所以这个偏移是一个结构体的地址,指向了一个2个成员的结构体,结构体+0的偏移是向上的偏移.+4的位置是向下的偏移,我们的父类+上这个偏移就能找到爷爷类了.
看其反汇编代码:
1.main函数下的构造

看出一个特征,push 1了,为什么?
因为要判断是否构造爷爷类,填写爷爷类的虚表,所以push 1,而当父类构造的时候,爷爷类就不要构造了.
2.构造内部.

1.首先和参数进行比较,判断是否为1, 相等就跳转,不相等就指向,
2.因为条件没有跳转,所以编译器首先给父类填写偏移.
3.如果跳转了,可以看到push 0.然后调用父类构造,其内部一样的判断是否构造爷爷类
4.最后构造爷爷类.
识别这个很简单了.
1.看是否构造
2.找偏移,也就是编译器填写偏移的位置,通过偏移的位置加上父类当前位置看一下是不是爷爷类的位置
3.会有两次写虚表的行为,一个是自身改,一个是基类改
4.总共会修改三处虚表,两个父类,一个祖先类的虚表.
表格

C++反汇编第四讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.的更多相关文章
- C++反汇编第五讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.
C++反汇编第五讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式. 目录: 1.多重继承在内存中的表现形式 多重继承在汇编中的表现形式 2.菱形继承 普通的菱形继承 虚继承 汇编中的表现形 ...
- C++反汇编第四讲,反汇编中识别继承关系,父类,子类,成员对象
C++反汇编第四讲,反汇编中识别继承关系,父类,子类,成员对象 讲解目录: 1.各类在内存中的表现形式 备注: 主要复习开发知识,和反汇编没有关系,但是是理解反汇编的前提. 2.子类继承父 ...
- C++中的类继承(4)继承种类之单继承&多继承&菱形继承
单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...
- python 菱形继承问题究极版
如果只是正常的菱形继承,经典类(python2中最后一个父类不继承object类)是深度优先,即会从左边父类开始一路走到底 新式类(最后一个父类继承了object类)是广度优先,即从左边父类开始继承, ...
- C++反汇编-菱形继承
学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记.马上就要出差了,回来后接着写吧. 一.概述 菱形继承是最复杂的对象结构,菱形结构会将单一继承与多重继承进行组合. ...
- 32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)
32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看) 昨天,大家可能都看了代码了,不知道昨天有没有在汇编代码的基础上,实现注入计算器. 如果没有,今天则会讲解,不过建议 ...
- PC逆向之代码还原技术,第四讲汇编中减法的代码还原
目录 PC逆向之代码还原技术,第四讲汇编中减法的代码还原 一丶汇编简介 二丶高级代码对应汇编观看. 1.代码还原解析: 三丶根据高级代码IDA反汇编的完整代码 四丶知识总结 PC逆向之代码还原技术,第 ...
- C++逆向分析----多重继承和菱形继承
多重继承 多重继承是指C++类同时继承两个类或两个以上的类. class Test { public: int num1; Test() { num1 = 1; } virtual void Proc ...
- 【数据库上】 第四讲 E-R模型基础知识
第四讲 E-R模型基础知识 一.数据库设计过程 数据库设计的关键阶段? 各个阶段设计的主要任务? 基础条件:清楚一个应用系统的功能需求与数据需求(直接与用户交互.数据流程图示例/UML类图等) 核心阶 ...
随机推荐
- 重读APUE(4)-fcntl和ioctl的区别
fcntl(File Control)-文件控制 ioctl(In/Out Control)-I/O控制 1. fcntl作用于文件,提供对文件的基础控制:ioctl作用于文件和设备对象,一般用来向设 ...
- ckpt convert to pb
import tensorflow as tf #from create_tf_record import * from tensorflow.python.framework import grap ...
- Qt编写安防视频监控系统1-通道切换
一.前言 通道切换在视频监控系统中是最基础的必备功能,一般都会提供1通道+4通道+6通道+8通道+9通道+16通道这几个通道切换,可能做得比较好的还会提供24通道+32通道的,这个可能对电脑的配置就有 ...
- JAVA 基础编程练习题42 【程序 42 求数字】
42 [程序 42 求数字] 题目:809*??=800*??+9*??+1 其中??代表的两位数,8*??的结果为两位数,9*??的结果为 3 位数.求??代表的两位数,及 809*??后的结 果. ...
- webdriervAPI(鼠标事件)
from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains 导 ...
- Opengl_入门学习分享和记录_03_渲染管线(三)借助顶点数组对象VAO提高绑定属性效率
目前我们已经知道了,如果想要顶点着色器解释理解我们的输入数据,就必须要按照以下繁琐的步骤:第一步:将输入的数据复制一份到缓冲区,供OpenGL使用.而这块新出现的区域由VBO管理和表示.(若有多个输入 ...
- 设计模式七大原则(C++描述)
前言 最近在学习一些基本的设计模式,发现很多博客都是写了六个原则,但我认为有7个原则,并且我认为在编码中思想还是挺重要,所以写下一篇博客来总结下 之后有机会会写下一些设计模式的博客(咕咕咕...... ...
- 《精通并发与Netty》学习笔记(12 - 详解NIO (三) SocketChannel、Pipe)
一.SocketChannelJava NIO中的SocketChannel是一个连接到TCP网络套接字的通道.可以通过以下2种方式创建SocketChannel: 打开一个SocketChannel ...
- 简单记录一下虚拟机中安装Linux的流程以及部分软件的安装命令
一,虚拟机使用的是VMware9 ,linux使用的是服务器中用的比较多的CentOS6.4.稍后我会把这两个版本放到网盘中,需要的朋友可以去下载: 网盘地址: 二,VM的安装比较简单,基本上按照网上 ...
- golang 二维切片
初始化: res := make([][length]int, length), 例如: res := make([][2]int, 10) fmt.Println(res) 输出: [[0 0] [ ...