C++反汇编第五讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式....
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 0; }
通过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,构造父类2之后的内存结构图,最后子类自己的构造中需要复写两个父类自己的虚表.而且自己扩展的虚函数会放在父类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 0; }
通过上面我们可以得出一个逻辑图:
单其实反汇编那种不是这样的.
从反汇编和内存中可以看出,每一个父类都有一个自己的爷爷类.而且每个父类构造爷爷类的时候,都会填写爷爷类的虚表,并且在自己的构造中对其复写(重写)
所以形成了下面这样的图
所以我们修改我们的高级代码.
int main(int argc, char* argv[]) {Child MyChild;//MyChild.m_int = 1; //重点是这句MyChild.Father1::m_int = 1;return 0; }
我们调用爷爷类中的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.总共会修改三处虚表,两个父类,一个祖先类的虚表.
表格
转载于:https://www.cnblogs.com/iBinary/p/8035783.html
C++反汇编第五讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式....相关推荐
- 内存首地址为1000h_C++虚继承,菱形继承,内存分布
前言 在叙述C++虚继承之前,我先给大家抛出一个问题.例如现在有4个类,分别是class A, class B, class C, class D.它们的关系如下图. 如上如所示,class B和cl ...
- 菱形继承,多继承,虚继承、虚表的内存结构全面剖析(逆向分析基础)
// 声明:以下代码均在Win32_Sp3 VC6.0_DEBUG版中调试通过.. 在逆向还原代码的时候,必须得掌握了菱形继承,多继承,虚继承虚函数的内存虚表结构.所以,这篇文章献给正在学习C++ ...
- C++57个入门知识点_50 菱形继承与虚继承(C++中语法允许多重继承造成菱形继承;会造成近亲结婚的问题;可以通过虚继承的方式解决;实际项目中不多用多重继承)
上篇C++57个入门知识点_49 多重继承与组合(一个类同时具有多个类的属性的方法:多重继承或者组合:多重继承:一个类同时继承多个类:多重继承构造和析构的顺序与普通继承类似:组合:类中包含多个成员对象 ...
- C++内存分布之菱形继承(无虚函数)
菱形继承的定义是:两个子类继承同一父类,而又有子类同时继承这两个子类.例如a,b两个类同时继承c,但是又有一个d类同时继承a,b类.探究的过程还是很有趣的. 菱形继承的内存布局探究花了我几天时间,探究 ...
- Python基础教程:菱形继承问题
一.类的分类 1.1 新式类 继承了object的类以及该类的子类,都是新式类 Python3中所有的类都是新式类 1.2 经典类 没有继承object的类以及该类的子类,都是经典类 只有Python ...
- C++之继承探究(十一):多重继承、菱形继承、虚继承、二义性问题及其解决方案
前文:C++之继承探究(十):抽象基类与纯虚函数 多重继承: 例1:两个父类中的同名函数如何区分示例 解决方法:在子类对象调用print( )函数时加上某个父类的作用域符号. 附上例代码: //小问学 ...
- 钻石问题(菱形继承问题) 和虚继承
在C++中,什么叫做钻石问题(也可以叫菱形继承问题),怎么避免它? 下面的图表可以用来解释钻石问题. 假设我们有类B和类C,它们都继承了相同的类A.另外我们还有类D,类D通过多重继承机制继承了类B和类 ...
- Java接口存在的意义以及如何解决菱形继承问题
可以说接口存在的目的就是为了解决菱形继承问题.我们用例子来去讲解这个问题. 多重继承 最好的办法就是使用多重继承 新建一个宠物类,让猫和狗都去继承宠物类的属性和方法.但是这样就会导致一个问题.即菱形继 ...
- 详解虚函数的实现过程之菱形继承(5)
大家看到标题,会不会菱形继承的虚表会不会是重复的呢?祖父类的虚表会不会在子类会不会是两份相同呢?那么我们一起来探索一下吧,冲冲冲!! 首先我们来分析一下: 它一共定义了四个类,分别为CFurnitur ...
最新文章
- 58姚劲波:从不裁员,只有淘汰
- 检测含有中文字符串的实际长度
- ASP.NET中如何实现负载均衡
- 鸟哥的Linux私房菜(服务器)- 第十章、申请合法的主机名
- java二级考点速记_同学,你要的考点速记口诀汇总篇来啦,速记!
- IOS基础使用PCH文件全局定义宏常量
- 云端计算机可以玩游戏么,手机掌上云电脑是什么?为什么可以玩PC游戏?
- storm中的乐器 wolven_Wolven Storm 风雪狼踪
- 北京集训②DAY1 Morning
- Linux中的VMware共享文件夹
- 构建OctoberCMS插件:Google Analytics(分析)
- C++ Beep()演奏简谱的改进以及实现背景音乐
- 免费天气API,全国天气 JSON API接口,可以获取五天的天气预报
- SpringBoot整合使用XXL-JOB
- c语言getch退出程序,用getch()时怎么样清除输入缓冲
- 【毕业设计_课程设计】面向高考招生咨询的问答系统设计与实现(源码+论文)
- ORACLE连接不上 Linux网络 端口 问题判断
- nodejs的windows版本安装
- 关于Win10家庭版修改用户名的问题
- 星志远电商:拼多多头像如何保存?
热门文章
- 团队开发——个人工作总结01
- lintcode:合并排序数组
- RMAN-06023: no backup or copy of datafile 6 found to restore
- 要做互联星空的SP接口,一点头绪都没有
- MySql应用原理分析系列文章目录
- Android研发中对String的思考(源码分析)
- JavaScript 与java中数组的区别
- Linux之强大的selinux
- Codeforces Round #445 div.2 D. Restoration of string 乱搞
- lambda表达式封装对数据库的查询