分享
 
 
 

对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测(上)

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测(下)

CSDN烤鸡翅膀

读过我以前写的文章的网友,都知道我是一个喜欢“刨根问底”、“死钻牛角尖”的家伙。最近由于工作需要转学DELPHI,在接触Object Pascal之后,果然领会到了它的整洁和优美,怪不得连《程序设计语言:设计与实现》一书的作者也称赞pascal是“一种极优美的语言”。但在学习过程中遇到了好多问题,特别是对于像我这样由C++转至OP[对Object Pascal的简称]学习的人,由于两种语言风格不同,问号就会更多了。其中,OP和C++语言的一个很大的区别就是:类对象[或称之为类实例]的内存分配机制不同。其中有两方面要说:

一、什么时候分配?

在C++中,定义了对象,那么马上分配其内存,之后调用其构造函数,这个内存可能在堆中,也可能在栈内,也可能在全程数据区内。但OP却截然不同,定义一对象,如:obj : TObject;只是为其分配了4字节的一个指针空间,而真正的对象空间还没有分配,那怎么用?在使用前当然要给对象分配空间,不然就会造成访问内存出错,给对象分配空间的办法也很简单:

obj := Tobject.Create;

就OK,这个对象空间是分配在堆内的,大家知道,栈内空间可以在使用期过后自动回收,但堆内存需要程序员自己管理,所以在使用完类对象之后,别忘了 obj.Free [真正实现析构的是obj.Destroy,但obj.free是一种更安全的方式]。

“什么时候分配”这个问题在OP和C++上的答案确实不同,但还不至于让我“疑惑”。知道了OP类对象是通过调用这样的语句(构造函数):obj := Tobject.Create;来得到堆内存的,但在这个处理细节上,编译器在内部是如何实现分配堆内存的呢?

请看下一个问题:

二、OP编译器是如何分配的内存?

首先要感谢Lippman的《Inside C++ Ojbect Model》,这是一本不可多得的好书,她告诉了你对于C++编译器实现的一些你最迷惑、也是最想关心的细节,但不知DELPHI业界内有没有这样一本书,可以让我清楚的了解到OP编译器具体[具体到每个细节]是如何给一个类对象分配堆内存的 [如果有这样的书,您一定要通知我:coder@vip.sina.com] ?

我大胆的做了猜测!

一些小动作都是在Tobject类内部事先已经定义好的!下面让我们来关注一下这几个Tobject类方法(Tobject定义于System.pas):

TObject = class

……

constructor Create;

procedure Free;

class function InitInstance(Instance: Pointer): TObject;

procedure CleanupInstance;

class function InstanceSize: Longint;

class function 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、

class 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;

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时只是想通过调用它知道得划分多少堆内存,但还没有正式分配堆内存,也就是说,VPTR、X、Y还不存在[但VMT是和类一同建立起来的,它包含了和类有关的一些信息,如类实例需要请求的堆内存的大小等等],当然这个Self也就不能是b的值了,我猜测它的内容是VMT中index = 0的格子的Address,只有这样,这里的代码和下面要讲的代码才能被正常解释,但,Self是怎么被Assigned为这个值的,我想是编译器所做的处理吧。

这样,Result := PInteger(Integer(Self) + vmtInstanceSize)^自然得到了类对象所需要堆内存大小的信息!

为了证明我上面的猜测是正确的,大家可以实验以下代码:

var

b :Tbase;

size_b : Integer;

begin

b := 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: Integer): Pointer;

它在System.pas 中的定义如下:

function _GetMem(Size: Integer): Pointer;

{$IF Defined(DEBUG) and Defined(LINUX)}

var

Signature: PLongInt;

{$IFEND}

begin

if Size > 0 then

begin

{$IF Defined(DEBUG) and Defined(LINUX)}

Signature := PLongInt(MemoryManager.GetMem(Size + 4));

if Signature = nil then

Error(reOutOfMemory);

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 function TObject.InitInstance(Instance: Pointer): TObject;

{$IFDEF PUREPASCAL}

var

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;

if IntfTable <> nil then

for I := 0 to IntfTable.EntryCount-1 do

with IntfTable.Entries[I] do

begin

if VTable <> nil then

PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);

end;

ClassPtr := ClassPtr.ClassParent;

end;

Result := Instance;

end;

{$ELSE}

asm

PUSH EBX

PUSH ESI

PUSH EDI

MOV EBX,EAX

MOV EDI,EDX

STOSD

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,ESP

@@0: MOV ECX,[EBX].vmtIntfTable

TEST ECX,ECX

JE @@1

PUSH ECX

@@1: MOV EBX,[EBX].vmtParent

TEST EBX,EBX

JE @@2

MOV EBX,[EBX]

JMP @@0

@@2: CMP ESP,EDX

JE @@5

@@3: POP EBX

MOV ECX,[EBX].TInterfaceTable.EntryCount

ADD EBX,4

@@4: MOV ESI,[EBX].TInterfaceEntry.VTable

TEST ESI,ESI

JE @@4a

MOV EDI,[EBX].TInterfaceEntry.IOffset

MOV [EAX+EDI],ESI

@@4a: ADD EBX,TYPE TInterfaceEntry

DEC ECX

JNE @@4

CMP ESP,EDX

JNE @@3

@@5: POP EDI

POP ESI

POP EBX

end;

{$ENDIF}

刚才知道_GetMem已经得到了堆内存空间,而我们现在要讨论的这个方法是进行一些必须的初始化。其它代码不管,只看这两句:

FillChar(Instance^, InstanceSize, 0);

PInteger(Instance)^ := Integer(Self);

第一就是给类对象清零,现在我们知道为什么OP的类实例的字段会自动被初始化为零了吧[String就为空,指针就为nil]!

第二条语句,是让VTPR指针指向VMT表的0号格子[读者请参考结构图自行分析,此处也证明上面我对Self值的猜测的正确性]。

(未完待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有