struct Point { int x ; int y ; }; class CBase { public: CBase(void); ~CBase(void); public: Point point ; public: virtual void Draw(); virtual void Paint(); void PulicSet() ; private: int nFinsh ; };
//构造函数
CBase::CBase(void) { nFinsh = 65535 ; point.x = 55 ; point.y = 44 ; }
从上面的这2段代码开始说起,看构造函数,我们只能看到他初始化了nFind,point 这2个成员对象,那么 构造函数真的只做的这些操作么?
从汇编来看一下它到底还做了什么
01361560 > 55 PUSH EBP 01361561 8BEC MOV EBP,ESP 01361563 81EC CC000000 SUB ESP,0CC 01361569 53 PUSH EBX 0136156A 56 PUSH ESI 0136156B 57 PUSH EDI 0136156C 51 PUSH ECX 0136156D 8DBD 34FFFFFF LEA EDI,DWORD PTR SS:[EBP-CC] 01361573 B9 33000000 MOV ECX,33 01361578 B8 CCCCCCCC MOV EAX,CCCCCCCC 0136157D F3:AB REP STOS DWORD PTR ES:[EDI] 0136157F 59 POP ECX ;ecx 为类对象的地址 01361580 894D F8 MOV DWORD PTR SS:[EBP-8],ECX 01361583 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 01361586 C700 04783601 MOV DWORD PTR DS:[EAX],OFFSET CBase@@6B@@std@@2W4_Openmode@12@BationDetails>@@0IB@@QAEXH_N@Zentry_basee ; 获取到指向类的虚函数表 0136158C 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 0136158F C740 0C FFFF0000 MOV DWORD PTR DS:[EAX+C],0FFFF ;初始化nFinsh 01361596 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 01361599 C740 04 37000000 MOV DWORD PTR DS:[EAX+4],37 ;初始化point.x 013615A0 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 013615A3 C740 08 2C000000 MOV DWORD PTR DS:[EAX+8],2C ;初始化point.y 013615AA 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 013615AD 5F POP EDI 013615AE 5E POP ESI 013615AF 5B POP EBX 013615B0 8BE5 MOV ESP,EBP 013615B2 5D POP EBP
我们看到了,在构造函数中 还把vptr 也就是指向虚函数表的地址放到了类对象的地址内,我们看下类对象的内存布局
0018F9C0 01367804 ;指向虚函数表 0018F9C4 00000037 ;point.x 0018F9C8 0000002C ;point.y 0018F9CC 0000FFFF ;nFinsh
接着继续来看一下vptr 内是什么样子的
01367804 >01361032 ;这里就是类所定义的虚函数 这个是Draw() 01367808 0136129E ; Paint()
我们可以以vptr[n]的形式来获取到类成员的虚函数,接下来继续跟进去,看看是不是我们想的那样
01361032 E9 D9050000 JMP 虚函数底.CBase::Drawtageif_errorement 01361610 > 55 PUSH EBP 01361611 8BEC MOV EBP,ESP 01361613 81EC CC000000 SUB ESP,0CC 01361619 53 PUSH EBX 0136161A 56 PUSH ESI 0136161B 57 PUSH EDI 0136161C 51 PUSH ECX 0136161D 8DBD 34FFFFFF LEA EDI,DWORD PTR SS:[EBP-CC] 01361623 B9 33000000 MOV ECX,33 01361628 B8 CCCCCCCC MOV EAX,CCCCCCCC 0136162D F3:AB REP STOS DWORD PTR ES:[EDI] 0136162F 59 POP ECX 01361630 894D F8 MOV DWORD PTR SS:[EBP-8],ECX 01361633 8BF4 MOV ESI,ESP 01361635 A1 10B33601 MOV EAX,DWORD PTR DS:[<&MSVCP90D.?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z>] 0136163A 50 PUSH EAX 0136163B 68 0C783601 PUSH OFFSET 虚函数底.??_C@_09HEPBPNEM@Base?5Draw?$AA@ate@12@Bpe?5causing?5los@?$AAc?$AAt?$AAo?$AAo?$AA... ; ASCII "Base Draw" 01361640 8B0D 0CB33601 MOV ECX,DWORD PTR DS:[<&MSVCP90D.?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A>] ; MSVCP90D.?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A 01361646 51 PUSH ECX 01361647 E8 30FBFFFF CALL 虚函数底.0136117C 0136164C 83C4 08 ADD ESP,8 0136164F 8BC8 MOV ECX,EAX 01361651 FF15 08B33601 CALL DWORD PTR DS:[<&MSVCP90D.??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@... ; MSVCP90D.??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z 01361657 3BF4 CMP ESI,ESP 01361659 E8 73FBFFFF CALL 虚函数底.013611D1 0136165E 5F POP EDI 0136165F 5E POP ESI 01361660 5B POP EBX 01361661 81C4 CC000000 ADD ESP,0CC 01361667 3BEC CMP EBP,ESP 01361669 E8 63FBFFFF CALL 虚函数底.013611D1 0136166E 8BE5 MOV ESP,EBP 01361670 5D POP EBP 01361671 C3 RETN
我们的猜想是对的 继续来看 下面一个虚函数地址是不是Paint
0136129E E9 ED030000 JMP 虚函数底.CBase::Paintageif_errorement
Bingo,我们猜想是正确的,接下来的文章我们会继续以汇编的角度 来看C++.并且我们会以汇编的方式访问一下虚函数