class cFa { public: cFa() {}; virtual ~cFa() { }; }; class cMo{ public: cMo() {}; virtual ~cMo() { }; }; class cChild : public cFa, public cMo { public: cChild() {}; virtual ~cChild() { }; }; void main() { cChild cCh; }
...... 001917DD pop ecx 001917FC mov ecx,dword ptr [this] ;取出this指针 001917FF call cFa::cFa (01913B1h) ;调用cFa构造函数 00191804 mov dword ptr [ebp-4],0 ;异常计数 可以忽略 0019180B mov ecx,dword ptr [this] ;获取this指针 0019180E add ecx,4 ;this指针偏移 00191811 call cMo::cMo (0191302h) ;调用cMo的构造函数 00191816 mov eax,dword ptr [this] ;获取this指针 00191819 mov dword ptr [eax],offset cChild::`vftable' (0198B4Ch) ;设置虚函数表 0019181F mov eax,dword ptr [this] ;获取this指针 00191822 mov dword ptr [eax+4],offset cChild::`vftable' (0198B58h) ;设置第二个虚函数表 00191829 mov dword ptr [ebp-4],0FFFFFFFFh 00191830 mov eax,dword ptr [this] ;返回this指针 ......
在上面的代码,我们看出构造函数的调用顺序是根据,从左到右的继承顺序来依次调用父类函数的
在调用cFa的构造函数时,直接传递了this指针,也就是cCh的地址,而在调用cMo的时候,传递的是this指针加4个字节的地址,也就是跳过了cFa所占的空间。
那么也就是说,父类对象在子类的内存布局的顺序和构造函数的调用顺序是一样的,那么现在cCh对象的内存结构如下图:
CFa: ...... 001918AD mov eax,dword ptr [this] 001918B0 mov dword ptr [eax],offset cFa::`vftable' (0198B34h) 001918B6 mov eax,dword ptr [this] ...... cMo: ...... 0019190D mov eax,dword ptr [this] 00191910 mov dword ptr [eax],offset cMo::`vftable' (0198B40h) 00191916 mov eax,dword ptr [this] ......
过滤掉了一些无用的代码,构造函数和非多重继承无任何区别,只是将传进来的对象的虚表换成自己的虚表。
有营养的部分来了:
两个构造函数结束后,出现了两个虚表赋值,为什么是两个虚表?
因为有两个父类,当调用父类函数的时候,需要通过偏移取得父类对象,传递指针,并调用函数。
这两个虚表在本例中意义不大,因为子类没有覆盖父类的虚函数,如果有覆盖的情况出现,这两个虚表中会保存子类覆盖的虚函数,和父类未覆盖的虚函数。那么调用就很简单了,虚表偏移。
析构函数就不上代码了,和构造函数的顺序恰巧相反。
如果在main函数中添加一行如下代码会发生什么?
cMo *pMo = (cMo*)&cCh;
上代码
class CAbstractBase { public: virtual void Show() = 0; }; class CAbstractChild : public CAbstractBase{ public: virtual void Show() { printf("show"); }; }; void main() { CAbstractChild cAb; cAb.Show(); }
;CAbstractChild 构造函数 ...... 00A1198F pop ecx 00A11990 mov dword ptr [this],ecx 00A11993 mov ecx,dword ptr [this] 00A11996 call CAbstractBase::CAbstractBase (0A11474h) 00A1199B mov eax,dword ptr [this] ;获取this指针 00A1199E mov dword ptr [eax],offset CAbstractChild::`vftable' (0A18B40h) ;虚函数表初始化 00A119A4 mov eax,dword ptr [this] ;返回this指针 ...... ;CAbstractBase 构造函数 ...... 00A118BF pop ecx 00A118C0 mov dword ptr [this],ecx 00A118C3 mov eax,dword ptr [this] ;获取this指针 00A118C6 mov dword ptr [eax],offset CAbstractBase::`vftable' (0A18B34h) ;虚函数表初始化 00A118CC mov eax,dword ptr [this] ;返回this指针 ......
看起来似乎和普通的继承没什么不同?
我们来看看CAbstractBase中的虚函数表(0A18B34h)指向的Show函数吧。
CAbstractBase的Show函数,没有实现代码,所以没有首地址,编译器防止误调用纯虚函数,将虚表中保存的纯虚函数的首地址项替换成了__purecall 函数,这个函数的作用就是结束程序,并返回错误码0x19。
其余与单类继承并无差别。
单类继承:
多重继承:
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
killpy __purecall f返回错误 啥意思 没看懂