虚函数实现机制
来源:互联网 发布:linux c实现web服务器 编辑:程序博客网 时间:2024/06/10 23:31
虚函数实现机制
(2007-04-24 09:07:26)虚函数
实现机制
内存分析
反汇编
注:本文系原创,如需转载,请注明出处和作者.
C++实现多态靠两种方案,静态的重载和动态的虚函数机制.下面通过VC6环境分析下虚函数的实现机制.
(其实在VC6中,重载的实现方式是名字粉碎,即给函数通过某种方式起个别名,实现"多态",个人认为并不是真正的多态)
代码经VC6+WINXP SP2编译通过.
#include <iostream.h>
class CFather
{
public:
virtual void virFunc();{cout<<"CFather::virFunc()"<<endl;}
CFather();
virtual ~CFather(); //虚函数定义
};
class CSon1 : public CFather
{
public:
void virFunc();{cout<<"CSon1::virFunc()"<<endl;} //重写
CSon1();
virtual ~CSon1();
};
class CSon2 : public CFather
{
public:
void virFunc();{cout<< "CSon2::virFunc()" <<endl;} //重写
CSon2();
virtual ~CSon2();
};
在父类中定义了虚函数virFunc,两个子类分别重写,默认也为virtual类型.main 函数中调用如下:
int main(int argc, char* argv[])
{
CFather father;
CSon1 son1;
CSon2 son2;
CFather* pObj = &father; //父类指针
pObj->virFunc();
pObj = &son1; //子类指针
pObj->virFunc();
pObj = &son2; //子类指针
pObj->virFunc();
return 0;
}
根据虚函数机制,结果应该为:
CFather::virFunc()
CSon1::virFunc()
CSon2::virFunc()
这里解释下,在C++中,基类指针可以指向其派生类,是实现动态多态的必要前提.
那么,在VC6中是如何实现虚函数呢?答案是虚函数表。看下编译时的内存情况。
当执行至CFather father 后,观察。(不同机器地址可能有所不同)
&father 的地址为:0x0012ff70,打开memory,该地址内容为:
0012FF70 1C 80 42 00 B0 FF 12 00 3B 6A 41 00 00 00 00 00
首地址的4字节内容为:0042801c,这就是虚表的入口,查看memory,该地址内容为:
0042801C 46 10 40 00 64 10 40 00 00 00 00 00 43 46 61 74
由于虚表中存放的都是函数指针,所以,每4个字节为一个地址,可以从这个表中找到两个地址:0x00401046,0x00401064。分别对应virFunc和析构函数。
同理,看一下son2的情况,换个观察方式。
当执行至 CSon2时,变量监视窗口可以看到son2的__vfptr的值0x00428086,其下分别为__vfptr[0],__vfptr[1],值分别为0x00401014,0x00401023。有点眼熟,对,这就是son2类的虚表,只是在从CFather继承来的时候,重写的函数地址被覆盖了。
以上为编译时候内存的情况,通过分析我们可以得知,在实例化类的同时,实例的首地址存储其虚表的地址。当子类重写时,将其重写的地址覆盖从父类继承来的虚表。当不同指针调用时,根据其虚表选取不同的函数,实现多态性.也就是说,这样的调用是在运行时才绑定的,所以称为动态的多态性.
然后我们看下反汇编的情况。
编译时单步进入00401690 call @ILT+55(CFather::CFather) (0040103c)
004010E9 pop ecx
004010EA mov dword ptr [ebp-4],ecx
004010ED mov eax,dword ptr [ebp-4]
004010F0 mov dword ptr [eax],offset CFather::`vftable' (0042801c)
我们来分析这四行代码。
首先,pop ecx,
mov dword ptr [ebp-4],ecx
将ecx出栈,并将其值按两个字(4个字节)传给ebp-4中的地址。通过观察registers(寄存器窗口),ecx=0012ff70。这里的ecx其实就是实例对象的this指针。
然后,mov eax,dword ptr [ebp-4]
将this指针传值给寄存器eax。因为汇编中不允许两个内存地址的操作,所以必须
采用eax做中转.
最后,mov dword ptr [eax],offset CFather::`vftable' (0042801c)
使用offset,将虚表的地址按两个字传给eax中的地址,即this指针指向了虚表地址。这就可以解释为什么对象的首地址都是虚表的地址了。
子类实现方式同上,只是在call本身的构造之前,首先要进行父类的构造。
另外,在编译的时候,根据函数定义的先后顺序,在虚拟表中安排函数的位置,其实就是定义了一个函数指针的数组。当采用相应的对象调用函数时,在数组中相应的偏移寻找函数。
还要注意,一般在类定义的时候,构造函数不可以采用虚函数,而析构函数则最好采用虚拟函数。
有关虚拟函数的定义,概念等以及汇编知识等请参考相应资料。本文不再讨论。
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数实现机制
- 虚函数的实现机制
- 【转载】虚函数实现机制
- 虚函数表实现机制
- c++ 虚函数实现机制
- 虚函数的实现机制
- c++ 虚函数实现机制
- 虚函数的实现机制
- 虚函数的实现机制
- 虚函数的实现机制
- C++ 虚函数实现机制
- 虚函数的实现机制
- WIN7家庭版 是不是不支持VC6.0啊
- UBuntu中增加中文字符编码的方法(转载自CSDN)
- ubuntu下android环境配置详细步骤(转载学习用)
- 快速排序
- Java多态性详解——父类引用子类对象(网络转载)
- 虚函数实现机制
- 图的深度遍历与广度遍历(C++)
- 二叉排序树的查找、删除、插入
- 图片延迟加载 JS jQuery 插件 Lazy Load
- 一些排序算法程序
- 模拟‘熊猫烧香’过程
- linux进程控制-exec系列
- C语言详解 之 函数参数的实现
- ubuntu lucid(10.04)上安装google android SDK环境