




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测读过我以前写的文章的网友,都知道我是一个喜欢“刨根问底”、“死钻牛角尖”的家伙。最近由于工作需要转学DELPHI,在接触Object Pascal之后,果然领会到了它的整洁和优美,怪不得连程序设计语言:设计与实现一书的作者也称赞pascal是“一种极优美的语言”。但在学习过程中遇到了好多问题,特别是对于像我这样由C+转至OP对Object Pascal的简称学习的人,由于两种语言风格不同,问号就会更多了。其中,OP和C+语言的一个很大的区别就是:类对象或称之为类实例的内存分配机制不同。其中有两方面要说:一、什么时候分配?在C
2、+中,定义了对象,那么马上分配其内存,之后调用其构造函数,这个内存可能在堆中,也可能在栈内,也可能在全程数据区内。但OP却截然不同,定义一对象,如:obj : TObject;只是为其分配了4字节的一个指针空间,而真正的对象空间还没有分配,那怎么用?在使用前当然要给对象分配空间,不然就会造成访问内存出错,给对象分配空间的办法也很简单:obj := Tobject.Create;就OK,这个对象空间是分配在堆内的,大家知道,栈内空间可以在使用期过后自动回收,但堆内存需要程序员自己管理,所以在使用完类对象之后,别忘了 obj.Free 真正实现析构的是obj.Destroy,但obj.free是一
3、种更安全的方式。“什么时候分配”这个问题在OP和C+上的答案确实不同,但还不至于让我“疑惑”。知道了OP类对象是通过调用这样的语句(构造函数):obj := Tobject.Create;来得到堆内存的,但在这个处理细节上,编译器在内部是如何实现分配堆内存的呢?请看下一个问题:二、OP编译器是如何分配的内存?首先要感谢Lippman的Inside C+ Ojbect Model,这是一本不可多得的好书,她告诉了你对于C+编译器实现的一些你最迷惑、也是最想关心的细节,但不知DELPHI业界内有没有这样一本书,可以让我清楚的了解到OP编译器具体具体到每个细节是如何给一个类对象分配堆内存的 如果有这
4、样的书,您一定要通知我: ?我大胆的做了猜测!一些小动作都是在Tobject类内部事先已经定义好的!下面让我们来关注一下这几个Tobject类方法(Tobject定义于System.pas): TObject = class constructor Create; procedure Free; class function InitInstance(Instance: Pointer): TObject; procedure CleanupInstance; class function InstanceSize: Longint; class functi
5、on NewInstance: TObject; virtual; procedure FreeInstance; virtual; destructor Destroy; virtual; end;从方法的名称上我们能隐约的感觉到:NewInstance和FreeInstance肯定和类对象的构造和析构有些关联!先来分析一下NewInstance:class function TObject.NewInstance: TObject;begin Result := InitInstance(_GetMem(InstanceSize);end;只有一句代码,但却调用了三个其它方法:1、clas
6、s function TObject.InstanceSize: Longint;begin Result := PInteger(Integer(Self) + vmtInstanceSize);end;这个方法是OP类实现RTTI的一个重要方法,它能返回类对象所需要占用堆内存的大小,注意它并非是类对象所占有内存大小,因为类对象是一指针,那么在32位环境下,指针永远是4字节!大家可能对这句代码比较疑惑Result := PInteger(Integer(Self) + vmtInstanceSize);下面我定义一个OP类:TBase = class(TObject) x : Integer
7、; y : Double; constructor Create;end;然后分配内存:b : Tbase ;b := TBase.Create;我设想分配后的内存布局应是这样的按C+对象的内存考虑联想的:再来看这句:Result := PInteger(Integer(Self) + vmtInstanceSize);它的目的是取到VMT中Index = -40注意:常量vmtInstanceSize = -40的格子中的内容。大家看这里的Self变量是什么值呢?是b的值也就是VPTR的ADDRESS吗?绝对不是!因为程序在执行到TObject.InstanceSize时只是想通过调用它知道
8、得划分多少堆内存,但还没有正式分配堆内存,也就是说,VPTR、X、Y还不存在但VMT是和类一同建立起来的,它包含了和类有关的一些信息,如类实例需要请求的堆内存的大小等等,当然这个Self也就不能是b的值了,我猜测它的内容是VMT中index = 0的格子的Address,只有这样,这里的代码和下面要讲的代码才能被正常解释,但,Self是怎么被Assigned为这个值的,我想是编译器所做的处理吧。这样,Result := PInteger(Integer(Self) + vmtInstanceSize)自然得到了类对象所需要堆内存大小的信息!为了证明我上面的猜测是正确的,大家可以实验以下代码:v
9、ar b :Tbase;size_b : Integer;beginb := TBase.Create;ShowMessage(Format(InitanceSize of TBase : %d,b.InstanceSize);size_b := PInteger(PInteger(b) - 40);ShowMessage(Format(InitanceSize of TBase : %d,size_b);end;大家可以看到,两种方法得到的是同一个值!好,现在我们回过头来讲解TObject.NewInstance中要调用的第二个函数。2、function _GetMem(Size: Inte
10、ger): Pointer;它在System.pas中的定义如下:function _GetMem(Size: Integer): Pointer;$IF Defined(DEBUG) and Defined(LINUX)var Signature: PLongInt;$IFENDbegin if Size 0 then begin$IF Defined(DEBUG) and Defined(LINUX) Signature := PLongInt(MemoryManager.GetMem(Size + 4); if Signature = nil then Error(reOutOfMemo
11、ry); Signature := 0; Result := Pointer(LongInt(Signature) + 4);$ELSE Result := MemoryManager.GetMem(Size); if Result = nil then Error(reOutOfMemory);$IFEND end else Result := nil;end;具体代码就不分析了,但我们终于看到了OP中分配堆内存的具体函数,原来是OP是通过一个内存管理器MemoryManager来管理类对象所取得的堆内存空间的!TObject.NewInstance中第三个调用的方法:3、class fun
12、ction TObject.InitInstance(Instance: Pointer): TObject;$IFDEF PUREPASCALvar IntfTable: PInterfaceTable; ClassPtr: TClass; I: Integer;begin FillChar(Instance, InstanceSize, 0); PInteger(Instance) := Integer(Self); ClassPtr := Self; while ClassPtr nil do begin IntfTable := ClassPtr.GetInterfaceTable;
13、if IntfTable nil then for I := 0 to IntfTable.EntryCount-1 do with IntfTable.EntriesI do begin if VTable nil then PInteger(PChar(Instance)IOffset) := Integer(VTable); end; ClassPtr := ClassPtr.ClassParent; end; Result := Instance;end;$ELSEasm PUSH EBX PUSH ESI PUSH EDI MOV EBX,EAX MOV EDI,EDX STOSD
14、MOV ECX,EBX.vmtInstanceSize XOR EAX,EAX PUSH ECX SHR ECX,2 DEC ECX REP STOSD POP ECX AND ECX,3 REP STOSB MOV EAX,EDX MOV EDX,ESP0: MOV ECX,EBX.vmtIntfTable TEST ECX,ECX JE 1 PUSH ECX1: MOV EBX,EBX.vmtParent TEST EBX,EBX JE 2 MOV EBX,EBX JMP 02: CMP ESP,EDX JE 53: POP EBX MOV ECX,EBX.TInterfaceTable.
15、EntryCount ADD EBX,44: MOV ESI,EBX.TInterfaceEntry.VTable TEST ESI,ESI JE 4a MOV EDI,EBX.TInterfaceEntry.IOffset MOV EAX+EDI,ESI4a: ADD EBX,TYPE TInterfaceEntry DEC ECX JNE 4 CMP ESP,EDX JNE 35: POP EDI POP ESI POP EBXend;$ENDIF刚才知道_GetMem已经得到了堆内存空间,而我们现在要讨论的这个方法是进行一些必须的初始化。其它代码不管,只看这两句:FillChar(Ins
16、tance, InstanceSize, 0);PInteger(Instance) := Integer(Self);第一就是给类对象清零,现在我们知道为什么OP的类实例的字段会自动被初始化为零了吧String就为空,指针就为nil!第二条语句,是让VTPR指针指向VMT表的0号格子读者请参考结构图自行分析,此处也证明上面我对Self值的猜测的正确性。到了这里,你也许会说,说了半天,都是猜测,或许,OP编译器根本就不会调用那个TObject.NewInstance方法呢!问得好,再做实验!还是以上面的那个Tbase类为例,重载TObject.NewInstance方法,如下:TBase =
17、class(TObject) x : Integer; y : Double; class function NewInstance: TObject; override; procedure FreeInstance; override; constructor Create; end;实现constructor TBase.Create;begin self.x := 2; self.y := 3.14;end;procedure TBase.FreeInstance;begin inherited; ShowMessage(Format(Call %s.FreeInstance!,sel
18、f.ClassName);end;class function TBase.NewInstance: TObject;begin ShowMessage(Format(call %s.NewInstance,self.ClassName); result := inherited NewInstance;end;之后进行简单的声明对象:varb : Tbase;beginb := Tbase.Create; 在这里设断点!b.Free;end;通过对代码进行跟踪果然在一进入Create就马上调用NewInstance方法。说明:一定要重载它才能跟踪到它,在断点处,观察CPU,从反汇编后的代码中
19、可以发现,是先调用一个_ClassCreate,然后才调用NewInstance 用同样的方法可以分析出b.Free会最终调用到FreeInstance;来释放对象。我想基本上大的问题已经说请了,Object Pascal为了实现分配堆内存,在你调用构造器的时候:b := Tbase.Create;在构造方法内你的代码前,安插了代码调用NewInstance方法,析构时,则在析构函数中你的代码后,调用FreeInstance函数。那么,现在再来看这种情况:派生TBase = class(TObject) x : Integer; y : Double; class function NewIn
20、stance: TObject; override; procedure FreeInstance; override; constructor Create; end; TSub = class (TBase) m : Integer; n : Double; constructor Create; end;实现constructor TBase.Create;begin self.x := 2; self.y := 3.14;end;procedure TBase.FreeInstance;begin inherited; ShowMessage(Format(Call %s.FreeIn
21、stance!,self.ClassName);end;class function TBase.NewInstance: TObject;begin ShowMessage(Format(call %s.NewInstance,self.ClassName); result := inherited NewInstance;end; TSub constructor TSub.Create;begin inherited Create; 注意这里! self.m := 4; self.n := 12.32;end;我们已经知道,vars : Tsub;s := Tsub.Create;时,在进入Tsub.Create内部马上得到了它想要的内存这里是32字节,与内存对齐有关,类里含有最大数double:8字节,所以总长度要8的倍数,那么当:inherited Create;时,在Tbase.Create内部,还有内存分配的动作吗?我们可以通过三点证明:这里,Tbase.Create只是完成程序员给
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 网红营销策略与品牌社会责任的结合考核试卷
- 卫生陶瓷生产质量管理团队建设与管理培训考核试卷
- 窗帘与遮阳设计在提高居住舒适度中的应用考核试卷
- 医用橡胶制品生产质量控制效果评价考核试卷
- 家电配件耐候性测试中的光照老化试验分析考核试卷
- 质量管理体系与市场竞争力提升路径分析考核试卷
- 天使综合征课件
- 工厂仓管笔试题及答案
- 稻谷落雨测试题及答案
- 便利店数字化供应链合作伙伴关系管理考核试卷
- 2025至2030中国废铜行业发展现状及发展趋势与投资风险报告
- 血管内导管相关性血流感染预防与诊治2025
- 【高二下期末】广东省东莞市2021-2022学年高二下学期期末教学质量监测英语试题(解析版)
- 2025年普通高等学校招生全国统一考试数学试题(全国二卷)(有解析)
- 无人飞机农业植保应用技术 课件17、极飞P40农业无人飞机作业-3
- 呼吸病区进修管理制度
- 足浴转让合同协议书
- 2022-2023学年山东省济宁市兖州区人教版四年级下册期末考试数学试卷(原卷版)
- 新课程标准视角下项目式学习在中小学的有效实施途径
- 1.1中华人民共和国成立前各种政治力量 课件高中政治统编版必修三政治与法治
- 酒店采购培训课程
评论
0/150
提交评论