从汇编代码分析c++虚函数表真正原理

记得当年刚入行的时候经常看一些别人讲C++虚函数表,当时也不理解,别人怎么说就怎么信了。后来知道多了,发现以前的帖子很有问题,不知道你是否看过这样的图片。现在我用汇编分析真正虚函数是如何实现动态,如何生存虚函数表。如果你觉得有什么不对的地方欢迎交流。这个图片问题很大,我只是指出些这样帖子的人一些错误。

下面来看我写的一对父子类

class MyPreant
{
public:
	MyPreant();
	~MyPreant();

	virtual void DisShow();
	virtual void DisShow2();
private:
	int m_base;
};

class MyClass:public MyPreant
{
public:
	MyClass();
	MyClass(int a);
	~MyClass();

	virtual void DisShow();
	virtual void DisShow2();
private:
	int m_a;
};

下面我们来看调用的过程

MyPreant *p2 = new MyClass;
p2->DisShow();

p2->DisShow2();

	MyPreant *p2 = new MyClass;
00E70FF7 83 C0 01             add         eax,1  
00E70FFA 50                   push        eax  
00E70FFB 68 F0 37 E8 00       push        offset string "c:\\users"... (0E837F0h)  
00E71000 6A 0C                push        0Ch  
00E71002 E8 0F 08 FF FF       call        operator new (0E61816h) //调用new函数 返回地址生成在eax寄存器
00E71007 83 C4 0C             add         esp,0Ch  
00E7100A 89 85 08 FF FF FF    mov         dword ptr [ebp-0F8h],eax  
00E71010 C7 45 FC 00 00 00 00 mov         dword ptr [ebp-4],0  
00E71017 83 BD 08 FF FF FF 00 cmp         dword ptr [ebp-0F8h],0  
00E7101E 74 13                je          CMyRemoteInjectionDlg::OnBnClickedButton1+83h (0E71033h)  
00E71020 8B 8D 08 FF FF FF    mov         ecx,dword ptr [ebp-0F8h]  
00E71026 E8 3A 04 FF FF       call        MyClass::MyClass (0E61465h) //调用子类的构造函数 

这是一个父类的指针指向子类的对象,用来研究多态的真正流程下面我们来看具体代码


这个是new出来的内存地址之后我们进入子类构造函数查看源码

00E71D20 55                   push        ebp  
00E71D21 8B EC                mov         ebp,esp  
00E71D23 81 EC CC 00 00 00    sub         esp,0CCh  
00E71D29 53                   push        ebx  
00E71D2A 56                   push        esi  
00E71D2B 57                   push        edi  
00E71D2C 51                   push        ecx  
00E71D2D 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
00E71D33 B9 33 00 00 00       mov         ecx,33h  
00E71D38 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E71D3D F3 AB                rep stos    dword ptr es:[edi]  
00E71D3F 59                   pop         ecx  
00E71D40 89 4D F8             mov         dword ptr [this],ecx  //赋值给this指针
00E71D43 8B 4D F8             mov         ecx,dword ptr [this]  
00E71D46 E8 E7 FC FE FF       call        MyPreant::MyPreant (0E61A32h) //调用父类的构造函数
00E71D4B 8B 45 F8             mov         eax,dword ptr [this]  
00E71D4E C7 00 DC 35 E8 00    mov         dword ptr [eax],offset MyClass::`vftable' (0E835DCh)  //把虚函数表赋值给地址空间的初始位置
之后我们进入父类的构造函数里
00E71D90 55                   push        ebp  
00E71D91 8B EC                mov         ebp,esp  
00E71D93 81 EC CC 00 00 00    sub         esp,0CCh  
00E71D99 53                   push        ebx  
00E71D9A 56                   push        esi  
00E71D9B 57                   push        edi  
00E71D9C 51                   push        ecx  
00E71D9D 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
00E71DA3 B9 33 00 00 00       mov         ecx,33h  
00E71DA8 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E71DAD F3 AB                rep stos    dword ptr es:[edi]  
00E71DAF 59                   pop         ecx  
00E71DB0 89 4D F8             mov         dword ptr [this],ecx  
00E71DB3 8B 45 F8             mov         eax,dword ptr [this]  
00E71DB6 C7 00 B4 35 E8 00    mov         dword ptr [eax],offset MyPreant::`vftable' (0E835B4h)  //把父类的虚函数表放到地址空间位置

下面来看内存空间 父类的虚函数表位置


之后父类做构造函数的初始化,返回到子类的构造函数。

00E71D4E C7 00 DC 35 E8 00    mov         dword ptr [eax],offset MyClass::`vftable' (0E835DCh)  

这一句是把子类的虚函数表赋值给当前地址,也就是覆盖了父类的虚函数表地址。这就是实现多态的原因。


这块地址的虚函数表已经变为0x00e835dc,也就是子函数的续表。那么我们来看看子函数续表是否包含父类续表地址。也就是最开始那个图是否正确


明显只有2个函数地址说明了子类函数地址只包含自己的虚函数地址。下面我们看看父类虚函数表0x00E835B4


父类的虚函数表也是只有2个函数地址。

那么我们来看看具体是函数地址是否对应看代码

00E614FB E9 70 11 01 00       jmp         MyClass::DisShow2 (0E72670h)  
00E614FB 正好就是我们写的子类的函数地址MyClass::DisShow2
通过查看汇编码和内存我们清楚的知道c++虚函数动态绑定的原理,和续表保存的东西。以后学东西不能看别人怎么写你就怎么信,要学会自己看汇编。知道真正的原理才能了解更深。

猜你喜欢

转载自blog.csdn.net/u011569253/article/details/80950293