探索Delphi类与对象的内存结构

初次接触DELPHI对它提供的RAD快速编程模式颇感神奇,随手拖放及格控件设定些属性一个应用程序就诞生了,我正是被这种特性所吸引。随着深入,慢慢的窥探到了DELPHI的VCL体系,知道了随手拖放背后隐藏的秘密:一切都起源于VCL的对象体系,一切都是面对对象的编程思想。Object pascal就是是怎样实现这个体系的呢,它究竟是如何将面对对象的特性表现出来的呢,Delphi的类和对象究竟是以什么样的形式存在的呢。带着这些问题我翻阅了一些书籍,也借鉴了一些网友的成果,做了下面的探索。

动态内存与静态内存
程序需要执行必须先装载入内存,任何程序表现的数据都存在内存中。当程序运行时,系统首先将所有数据装载入内存,完成初始化,然后从入口地址开始执行代码。程序装载后即存在于内存空间中的数据我们称之为静态内存,运行过程中分配的内存我们称之为动态内存。Delphi的类是由编译期间决定的,编译完成后即固定在程序中,所以类是存在于静态内存中。对象是由运行期间创建的,所以对象属于动态内存。

\
注意:后面所提到的TObject均为泛指所有类,而非真正的TObject类

                                                        程序运行示意图

类的内存结构
       类的内存结构是固定的,编译完成后就无法改变。它主要存储了类的基本信息,派生对象内存大小,虚方法列表,动态方法列表,公开属性和方法列表(published),接口列表,TObject类的一些方法等等有关于构建对象所必须的信息。这些信息的存储位置在SYSTEM单元中有定义:

vmtSelfPtr           = -76;         指向虚方法表的指针

vmtIntfTable         = -72;          指向接口表的指针

vmtAutoTable         = -68;        指向自动化信息表的指针

vmtInitTable         = -64;          指向实例初始化表的指针

vmtTypeInfo          = -60;        指向类型信息表的指针,这里的数据对于RTTI来说非常重要,它指向一个PTypeInfo类型的指针,有兴趣可以看看TypInfo单元

vmtFieldTable        = -56;          指向域定义表的指针(我开始认为是Published Field,但实际查询时却为NIL)

vmtMethodTable       = -52;        指向方法定义表的指针(Published)

vmtDynamicTable      = -48;        指向动态方法表的指针

vmtClassName         = -44;       指向类名字符串的指针

vmtInstanceSize      = -40;          对象实例的大小

vmtParent            = -36;        指向父类的指针

vmtSafeCallException = -32 deprecated; 以下都是TOBJECT类的一些虚拟方法指针

vmtAfterConstruction = -28 deprecated;

vmtBeforeDestruction = -24 deprecated;

vmtDispatch          = -20 deprecated;

vmtDefaultHandler    = -16 deprecated;

vmtNewInstance       = -12 deprecated;

vmtFreeInstance      = -8 deprecated;

vmtDestroy           = -4 deprecated;

如果获取对象大小,可以使用以下代码:

Result := PInteger(Integer(TObject) + vmtInstanceSize)^;

其他各项可以依此类推。

l       静态方法

类的静态方法在编译期间就决定了它的地址,类只为所有的派生的对象提供统一的一份静态方法表,不会为每个对象复制一份,所以不必关心静态方法的存储(实际上静态方法也是和动态方法有序的排列在一块的,顺序与方法的实现顺序有关)

l       非静态方法

虚方法(Virtual)和动态方法(Dynamic)均为非静态方法,它们是用来实现面对对象的多态性的关键特性。通过这种特性,开发者可以根据需要在不同的子类中拥有不同的实现,从而使设计变得更加灵活。在具体实现中,编译器只需要将简单的改变表中方法指针的指向即可达到目的。从语法上讲虚拟方法和动态方法是没有任何区别的,凡是声明了该两种类型的方法,在子类中都可以通过override关键字进行覆盖。但实际上二者的实现是存在巨大差别的:

vmtSelfPtr(虚方法表的指针)实际上就是指向TObject位置,所以类的虚拟方法是依次排在TObject所指向的位置之后。

\
vmtDynamicTable(动态方法表的指针)指向的是动态方法表,动态方法表的结构与虚方法表的结构有所不同。

\
两者实现方式的不同体现了两者作用的不同。虚拟方法表包括本身以及以上的父类所有的虚拟方法的地址,调用时直接指向地址即可,好处在于速度极快,不需要查询,缺点在于占用了额外的内存。动态方法表则只保存自己本身所包含的动态方法表,如果调用者的动态方法不属于自己,则根据索引号往上级父类遍历查询得到方法的地址,好处在于不用保存父类的动态方法从而节省了内存,缺点在于搜索带来的效率下降。

l       接口表的指针

vmtIntfTable(接口表的指针)指向一块PInterfaceTable类型的接口信息表空间,vmtIntfTable只保存当前类所实现的接口表信息,不保存父类的接口表信息,创建对象时会根据vmtParent父类指针遍历获取所有父类的接口表信息插入对象内存空间。

l       Published Method表

vmtMethodTable(Published Method表)指向Published Method表有序排列,只存储当前类的Published Method表,得到父类的Published Method表需要往上遍历。

posted @ 2010-06-24 10:21  Max Woods  阅读(486)  评论(0编辑  收藏  举报