浅析C++继承的内存布局
2015-11-04 12:56
567 查看
代码如下
通过反汇编初步结果为
首先我们先看主函数,
00401070 push ebp
00401071 mov ebp,esp
00401073 push 0FFh
00401075 push offset __ehhandler$_main (0041f279)
0040107A mov eax,fs:[00000000]
00401080 push eax
00401081 mov dword ptr fs:[0],esp
00401088 sub esp,4Ch
0040108B push ebx
0040108C push esi
0040108D push edi
0040108E lea edi,[ebp-58h]
00401091 mov ecx,13h
00401096 mov eax,0CCCCCCCCh
0040109B rep stos dword ptr [edi]
这里的栈初始化有点不一样,规定基址ebp后,却压入了三个DWORD,也就是FFh, offset __ehhandler$_main (0041f279),还有eax,这点要注意。
Derived deriv;
0040109D lea ecx,[ebp-14h]
004010A0 call @ILT+45(Derived::Derived) (00401032)
004010A5 mov dword ptr [ebp-4],0
往下走,就是子类的构造函数,00401032是个跳转语句,跳转到了
Derived::Derived:
00401220 push ebp
00401221 mov ebp,esp
00401223 sub esp,44h
00401226 push ebx
00401227 push esi
00401228 push edi
00401229 push ecx
0040122A lea edi,[ebp-44h]
0040122D mov ecx,11h
00401232 mov eax,0CCCCCCCCh
00401237 rep stos dword ptr [edi]
00401239 pop ecx
0040123A mov dword ptr [ebp-4],ecx
0040123D mov ecx,dword ptr [ebp-4]
00401240 call @ILT+15(Base::Base) (00401014)
00401245 mov eax,dword ptr [ebp-4]
00401248 pop edi
00401249 pop esi
0040124A pop ebx
0040124B add esp,44h
0040124E cmp ebp,esp
00401250 call __chkesp (00408810)
00401255 mov esp,ebp
00401257 pop ebp
这里面我们要插入一句,C++里面总是说什么this指针,指向的是本对象的地址,这样讲有点云里雾里,不如我们用汇编来分析
00401088 sub esp,4Ch
这一句代码是主函数的,意思是申请一个大小为0x4C的栈空间,栈空间存放的是子类对象。既然有了子类对象,如何初始化呢
0040109D lea ecx,[ebp-14h]
004010A0 call @ILT+45(Derived::Derived) (00401032)
这里是讲ebp-14处的地址,其实也就是ebp-14h这个值,付给ecx,然后调用子类构造函数,为什么是ebp-14h呢,因为前面压入了三个双字变量,这里子类对象大小我们知道是父类大小+新增成员变量大小,为8,所以子类的对象的起始地址为ebp-14h(这里的14h,换成十进制,就是20)
图如下
本图从下往上,是栈的增长方向。
我们看主函数这一句,0040109D lea ecx,[ebp-14h],意思是把地址为ebp-14h处的单元的地址付给ecx,也就是ebp-14h付给ecx,ebp-14h是子类的首地址,连接往下四个字节,一共八个字节,是子类的总大小。
然后进入 004010A0 call @ILT+45(Derived::Derived) (00401032)
在子类的构造函数中我们可以看到,
00401239 pop ecx
0040123A mov dword ptr [ebp-4],ecx
0040123D mov ecx,dword ptr [ebp-4]
00401240 call @ILT+15(Base::Base) (00401014)
将传入的子类的首地址用ecx传入,在弹出,在保存在ebp-4h处,再传给ecx,并调用基类的构造函数。
我们这里的ecx,实际上存储的就是传说中的this指针了,也就是子类对象的首地址,通过传递这个地址,我们可以方便的对子类进行操作,比如对其成员变量进行赋值。
这一点算是分析完毕了,跳回去,我们接着分析子类构造函数。
看这句
00401240 call @ILT+15(Base::Base) (00401014)
这个意思是调用父类的构造函数,明显,此时ecx存储的还是子类传过来的地址
Base()
00401270 push ebp
00401271 mov ebp,esp
00401273 sub esp,44h
00401276 push ebx
00401277 push esi
00401278 push edi
00401279 push ecx
0040127A lea edi,[ebp-44h]
0040127D mov ecx,11h
00401282 mov eax,0CCCCCCCCh
00401287 rep stos dword ptr [edi]
00401289 pop ecx
0040128A mov dword ptr [ebp-4],ecx
19: {
20: printf("Base");
0040128D push offset string "Base" (00431020)
00401292 call printf (00408940)
00401297 add esp,4
21: }
这个就是我们在构造函数中调用的父类构造函数,可以看到这里面打印了一个字符串,并没有进行赋值什么的,如果进行赋值呢?比如Base构造函数里把从父类继承过来的m_nBase=2,这样应该会变为,mov [ecx],2
有兴趣的可以修改下源程序,然后看下这里的反汇编代码。
到这里,子类的构造函数就分析完了,我们接着分析
可以看到里面又调用了继承自父类的Setnumber函数,这里面还是用的ebp-14h进行传递,不多讲,SetNumber执行完后,ebp-14h这个地方,就变成了5,我们可以知道,这个双字,实际上是继承的父类的成员变量。
往下走,就是
这里可以看到,首先将形参自增1,变成6,然后
class Base { public : Base() { printf("Base"); } ~Base() { printf("~Base"); } void SetNumber(int number) { m_nBase=number; } int GetNumber() { return m_nBase; } private: int m_nBase; }; class Derived:public Base { public: void ShowNumber(int number) { SetNumber(number); m_nDerived=number+1; printf("%d",GetNumber()); printf("%d",m_nDerived); } int m_nDerived; }; int main() { Derived deriv; deriv.ShowNumber(5); return 0; }
通过反汇编初步结果为
首先我们先看主函数,
00401070 push ebp
00401071 mov ebp,esp
00401073 push 0FFh
00401075 push offset __ehhandler$_main (0041f279)
0040107A mov eax,fs:[00000000]
00401080 push eax
00401081 mov dword ptr fs:[0],esp
00401088 sub esp,4Ch
0040108B push ebx
0040108C push esi
0040108D push edi
0040108E lea edi,[ebp-58h]
00401091 mov ecx,13h
00401096 mov eax,0CCCCCCCCh
0040109B rep stos dword ptr [edi]
这里的栈初始化有点不一样,规定基址ebp后,却压入了三个DWORD,也就是FFh, offset __ehhandler$_main (0041f279),还有eax,这点要注意。
Derived deriv;
0040109D lea ecx,[ebp-14h]
004010A0 call @ILT+45(Derived::Derived) (00401032)
004010A5 mov dword ptr [ebp-4],0
往下走,就是子类的构造函数,00401032是个跳转语句,跳转到了
Derived::Derived:
00401220 push ebp
00401221 mov ebp,esp
00401223 sub esp,44h
00401226 push ebx
00401227 push esi
00401228 push edi
00401229 push ecx
0040122A lea edi,[ebp-44h]
0040122D mov ecx,11h
00401232 mov eax,0CCCCCCCCh
00401237 rep stos dword ptr [edi]
00401239 pop ecx
0040123A mov dword ptr [ebp-4],ecx
0040123D mov ecx,dword ptr [ebp-4]
00401240 call @ILT+15(Base::Base) (00401014)
00401245 mov eax,dword ptr [ebp-4]
00401248 pop edi
00401249 pop esi
0040124A pop ebx
0040124B add esp,44h
0040124E cmp ebp,esp
00401250 call __chkesp (00408810)
00401255 mov esp,ebp
00401257 pop ebp
这里面我们要插入一句,C++里面总是说什么this指针,指向的是本对象的地址,这样讲有点云里雾里,不如我们用汇编来分析
00401088 sub esp,4Ch
这一句代码是主函数的,意思是申请一个大小为0x4C的栈空间,栈空间存放的是子类对象。既然有了子类对象,如何初始化呢
0040109D lea ecx,[ebp-14h]
004010A0 call @ILT+45(Derived::Derived) (00401032)
这里是讲ebp-14处的地址,其实也就是ebp-14h这个值,付给ecx,然后调用子类构造函数,为什么是ebp-14h呢,因为前面压入了三个双字变量,这里子类对象大小我们知道是父类大小+新增成员变量大小,为8,所以子类的对象的起始地址为ebp-14h(这里的14h,换成十进制,就是20)
图如下
44h的空间(填充了0xCC) |
子类对象首地址 (ebp-14h) this (这里其实存放的是从父类继承的m_nBase) |
子类对象首地址+4 (ebp-10h) this+4 (这里存储的是子类新增的m_nDerived) 红色的两个框,8个字节,就是子类对象 |
压入的第三个双字(ebp-C) |
压入的第二个双字(ebp-8) |
压入的第一个双字(ebp-4) |
ebp |
我们看主函数这一句,0040109D lea ecx,[ebp-14h],意思是把地址为ebp-14h处的单元的地址付给ecx,也就是ebp-14h付给ecx,ebp-14h是子类的首地址,连接往下四个字节,一共八个字节,是子类的总大小。
然后进入 004010A0 call @ILT+45(Derived::Derived) (00401032)
在子类的构造函数中我们可以看到,
00401239 pop ecx
0040123A mov dword ptr [ebp-4],ecx
0040123D mov ecx,dword ptr [ebp-4]
00401240 call @ILT+15(Base::Base) (00401014)
将传入的子类的首地址用ecx传入,在弹出,在保存在ebp-4h处,再传给ecx,并调用基类的构造函数。
我们这里的ecx,实际上存储的就是传说中的this指针了,也就是子类对象的首地址,通过传递这个地址,我们可以方便的对子类进行操作,比如对其成员变量进行赋值。
这一点算是分析完毕了,跳回去,我们接着分析子类构造函数。
看这句
00401240 call @ILT+15(Base::Base) (00401014)
这个意思是调用父类的构造函数,明显,此时ecx存储的还是子类传过来的地址
Base()
00401270 push ebp
00401271 mov ebp,esp
00401273 sub esp,44h
00401276 push ebx
00401277 push esi
00401278 push edi
00401279 push ecx
0040127A lea edi,[ebp-44h]
0040127D mov ecx,11h
00401282 mov eax,0CCCCCCCCh
00401287 rep stos dword ptr [edi]
00401289 pop ecx
0040128A mov dword ptr [ebp-4],ecx
19: {
20: printf("Base");
0040128D push offset string "Base" (00431020)
00401292 call printf (00408940)
00401297 add esp,4
21: }
这个就是我们在构造函数中调用的父类构造函数,可以看到这里面打印了一个字符串,并没有进行赋值什么的,如果进行赋值呢?比如Base构造函数里把从父类继承过来的m_nBase=2,这样应该会变为,mov [ecx],2
有兴趣的可以修改下源程序,然后看下这里的反汇编代码。
到这里,子类的构造函数就分析完了,我们接着分析
deriv.ShowNumber(5);
void ShowNumber(int number) 41: { 00401110 push ebp 00401111 mov ebp,esp 00401113 sub esp,44h 00401116 push ebx 00401117 push esi 00401118 push edi 00401119 push ecx 0040111A lea edi,[ebp-44h] 0040111D mov ecx,11h 00401122 mov eax,0CCCCCCCCh 00401127 rep stos dword ptr [edi] 00401129 pop ecx 0040112A mov dword ptr [ebp-4],ecx 42: SetNumber(number); 0040112D mov eax,dword ptr [ebp+8] 00401130 push eax 00401131 mov ecx,dword ptr [ebp-4] 00401134 call @ILT+10(Base::SetNumber) (0040100f) 43: m_nDerived=number+1; 00401139 mov ecx,dword ptr [ebp+8] 0040113C add ecx,1 0040113F mov edx,dword ptr [ebp-4] 00401142 mov dword ptr [edx+4],ecx 44: printf("%d",GetNumber()); 00401145 mov ecx,dword ptr [ebp-4] 00401148 call @ILT+35(Base::GetNumber) (00401028) 0040114D push eax 0040114E push offset string "%d" (0043101c) 00401153 call printf (00408940) 00401158 add esp,8 45: printf("%d",m_nDerived); 0040115B mov eax,dword ptr [ebp-4] 0040115E mov ecx,dword ptr [eax+4] 00401161 push ecx 00401162 push offset string "%d" (0043101c) 00401167 call printf (00408940) 0040116C add esp,8 46: 47: }
可以看到里面又调用了继承自父类的Setnumber函数,这里面还是用的ebp-14h进行传递,不多讲,SetNumber执行完后,ebp-14h这个地方,就变成了5,我们可以知道,这个双字,实际上是继承的父类的成员变量。
往下走,就是
m_nDerived=number+1; 00401139 mov ecx,dword ptr [ebp+8] 0040113C add ecx,1 0040113F mov edx,dword ptr [ebp-4] 00401142 mov dword ptr [edx+4],ecx
这里可以看到,首先将形参自增1,变成6,然后
mov edx,dword ptr [ebp-4]
将首地址付给edx寄存器,然后将6,付给了edx+4处的内存,edx+4处的内存是哪里?我们看图,就知道,是首地址往下四个字节,也就是ebp-10h,也就是this+4处,也就是子类新增的成员变量的位置。
后面就基本结束了。
反汇编确实是利器,剖开C++晦涩的语法糖,直窥本源。
相关文章推荐
- [2024 ]:C语言合法标识符 (简单易错)
- C语言extern和static
- C++第十三课 结构体
- c++父类子类同名变量
- 今日学习札记——C++指针3(11.4)
- c语言学习之基础知识点介绍(十三):枚举的介绍和使用
- c++中,结构体和联合体的区别
- c++ const放置的位置
- C++一种智能指针的实现
- 【c++】标准模板库STL入门简介与常见用法
- c++ 线程池
- c++中的引用于指针注意点
- C++资源释放
- 黑马程序员-------C语言回顾-二维数组
- 顺序容器string操作介绍
- 记录C语言入门学习之二
- 异常问题
- C/C++21个重点笔记(常考笔试面试点)
- C++中调用Python脚本
- C++基础——非类型模板参数