我的文章-《剖析Delphi中的构造和析构》

2008-04-09 04:23:18来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

剖析Delphi中的构造和析构

1 Delphi中的对象模型: 2
1.1 对象名表示什么? 2
1.2 对象存储在哪里? 2
1.3 对象中存储了什么?它们是如何存储的? 3
2 构造函数与创建对象 5
2.1 什么是构造函数?(“特殊的”类方法) 5
2.2 对象的创建的全过程 5
2.3构造函数另类用法(使用类引用实现构造函数的多态性) 6
3 析构函数与销毁对象 7
3.1 什么是析构函数(“天生的”虚方法) 7
3.2 对象销毁的全过程 7
3.3 destroy, free, freeAndNil, release用法和区别 7
4 VCL构造&析构体系结构 8
5 正确使用构造函数和析构函数 9

剖析Delphi中的构造和析构
摘 要: 本文通过对VCL/RTL的研究,来剖析构造函数和析构函数的实现机制和VCL中对象的体系结构,并说明如何正确地创建和释放对象。
关键字: 构造,析构,创建对象,销毁对象,堆,栈,多态。
作 者: majorsoft

问题
Delphi中构造函数和析构函数的实现机制是什么?和C 有何不同?如何做到正确地创建和释放对象?
解决思路
如何正确使用构造和析构是我们在使用Delphi过程中经常遇到的问题,在大富翁论坛中的Oriented Pascal栏目有不少相关帖子(详见相关问题),本人也曾遇到过类似的问题,下面通过对VCL/RTL源代码的研究,来理解构造函数和析构函数的实现机制。
1 Delphi中的对象模型:
1.1 对象名表示什么?
与C 不同,Delphi中的对象名(也可以称做变量)表示对象的引用,并不表示对象本身,相当于指向对象的指针,这就所谓的“对象引用模型”。如图所示:
Obj(对象名) 实际的对象

Vmt 入口地址

数据成员

图1对象名引用内存中的对象
1.2 对象存储在哪里?
每个应用程序将分配给其运行的内存分为四个区域:

代码区(Code area)
全局数据区(data area)
堆区(heap area)
栈区(stack area)

图2 程序内存空间
代码区:存储程序中程序代码,包括所有的函数代码
全局数据区:存储全局数据。
堆区:又叫“自由存储区”,存储动态数据(在Delphi中包括对象和字符串)。作用域为整个应用程序的整个生命周期直到调用了析构方法。
栈区:又叫“自动存储区”存储程序中的局部数据,在C 中,局部变量实际上是auto类型的变量。作用域为函数内部,函数调用完系统就立即回收栈空间。
在C 中,对象既可创建在堆(heap)上,也可以创建在栈(stack)中,还可以在全局数据中创建对象,故C 有全局对象、局部对象、静态对象和堆对象四种对象之说。而在Delphi中,所有的对象都是建立堆(heap)存储区上,所以Delphi构造函数不能自动被调用,而必须由程序员自己调用(在设计器拖动组件,此时对象由Delphi创建)。下面的程序说明Delphi和C 中创建对象的区别:
在Delphi中:
Procedure CreateObject(var FooObjRef:TFooObject);
begin
FooObjRef:=TfooObject.create;
//由程序员调用,过程调用完之后,对象依然存在.不需要进行拷贝
FooObject.caption=’I am created in stack of CreateObject()’;
End;
而在C 中:
TfooObject CreateObject(void);
{
TfooObject FooObject;//创建局部对象
// static TfooObject FooObject;//创建静态局部对象
//对象自动调用默认的构造函数进行创建,对象此时在函数栈中创建
FooObject.caption=’I am created in stack of CreateObject()’;
return FooObject;
//返回的时候进行了对象拷贝,原来创建的对象随函数的调用结束后,自动销毁}
TfooObject fooObject2;//创建全局对象。
void main();
{ TFooObject* PfooObjec=new TfooObject;
//创建堆对象。函数调用完之后,对象依然存在,不需要进行拷贝。}
1.3 对象中存储了什么?它们是如何存储的?
与C 不同的是,Delphi中的对象只存储了数据成员和虚拟方法表(vmt)的入口地址,而没有存储方法,如图所示:
对 象 虚拟方法表 代码段

Vmt地址
name:String
width:integer;
ch1:char;

Proc1
Func1

procn
funcn



图 3 对象的结构 …
也许你对上面的说法存在着些疑问,请看下面的程序:
TsizeAlignTest=class
private
i:integer;
ch1,ch2:char;
j:integer;
public
procedure showMsg;
procedure virtMtd; virtual;
end;

memo1.Lines.Add(inttostr(sizeTest.InstanceSize) '''':InstanceSize'''');
memo1.Lines.Add(inttostr(integer(sizeTest)) ''''<-start Addr'''');
memo1.Lines.Add(inttostr(integer(@(sizeTest.i))) ''''<-sizeTest.i'''');
memo1.Lines.Add(inttostr(integer(@(sizeTest.ch1))) ''''<-sizeTest.ch1'''');
memo1.Lines.Add(inttostr(integer(@(sizeTest.ch2))) ''''<-sizeTest.ch2'''');
memo1.Lines.Add(inttostr(integer(@(sizeTest.j))) ''''<-sizeTest.j'''');
结果显示:
16:InstanceSize
14630724<-start Addr
14630728<-sizeTest.i
14630732<-sizeTest.ch1
14630733<-sizeTest.ch2
14630736<-sizeTest.j
数据成员和vmt入口地址就占了16个字节!,两个成员函数showMsg, virtMtd在对象的存储区中根本没占空间。
那么成员函数到底存储在哪儿呢?由于Delphi是基于RTL(运行时类型库)的,所有的成员函数都在类中存储,成员函数实际上就是方法指针,它指向成员函数的入口地址,该类的所有对象共享这些成员函数。那么怎样找到成员函数的入口地址呢?对于静态函数,这个工作由编译器来完成的,在编译过程中,根据类对象引用/指针的类型,即直接在类来中找到成员函数的入口地址(此时并不需要对象存在),这也就是所谓的静态绑定;而对于虚方法(包括动态方法),则是通过在运行时的对象的虚拟方法表vmt入口地址(即对象的前四个字节,此时对象一定要存在,否则就会导致指针访问出错),来找到成员函数的入口地址,这也就是所谓的动态绑定。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:我的文章-《剖析Delphi中的多态》

下一篇:四舍五入的BUG