我想知道在调用New和Dispose时内部会发生什么。Delphi帮助文档给出了一个合理的解释,但是如果一个人想要编写自己的New和Dispose,需要做些什么呢?这两种方法内部调用哪些方法,或者是否都是汇编语言实现的?
我并不想编写自己的New和Dispose。我只是非常好奇这两个方法的内部工作原理。
我想知道在调用New和Dispose时内部会发生什么。Delphi帮助文档给出了一个合理的解释,但是如果一个人想要编写自己的New和Dispose,需要做些什么呢?这两种方法内部调用哪些方法,或者是否都是汇编语言实现的?
我并不想编写自己的New和Dispose。我只是非常好奇这两个方法的内部工作原理。
New
会执行以下操作:
GetMem
为新对象分配内存。Dispose
则相反:
FreeMem
释放内存。New
和Dispose
都是内置函数。这意味着编译器对它们有额外的了解,并能够根据相关类型来确定它们的实现方式。New
将优化为一个简单的GetMem
调用。如果类型具有托管字段,则New
将使用调用System._New
,该函数执行上述步骤。
Dispose
的实现方式也类似。对于非托管类型,可以使用一个简单的FreeMem
调用,否则需要调用System._Dispose
。System._New
的实现方式如下:function _New(Size: NativeInt; TypeInfo: Pointer): Pointer;
begin
GetMem(Result, Size);
if Result <> nil then
_Initialize(Result, TypeInfo);
end;
请注意,我刚刚展示了PUREPASCAL
变量以便简化。调用GetMem
足够简单。调用System._Initialize
则要复杂得多。它使用TypeInfo
参数查找对象中包含的所有托管类型并对其进行初始化。这是一个递归过程,因为例如记录可能包含自身是结构类型的成员。你可以在RTL源代码中看到所有细节。
至于System._Dispose
,它调用System._Finalize
然后调用FreeMem
。而System._Finalize
与System._Initialize
非常相似,只不过它是用于终止托管类型而不是初始化它们。
长期以来,对于对性能敏感的Delphi用户来说,System._Initialize
和System._Finalize
都是基于运行时类型信息实现的问题。这些类型在编译时已知,编译器可以被编写成内联初始化和终止,这将导致更好的性能。
这是如何编写自己的New()
和Dispose()
函数,并使用自己的记录初始化/终止:
uses
TypInfo;
procedure RecordInitialize(Dest, TypeInfo: pointer);
asm
{$ifdef CPUX64}
.NOFRAME
{$endif}
jmp System.@InitializeRecord
end;
procedure RecordClear(Dest, TypeInfo: pointer);
asm
{$ifdef CPUX64}
.NOFRAME
{$endif}
jmp System.@FinalizeRecord
end;
function NewRec1(TypeInfo: pointer): pointer;
begin
GetMem(result, GetTypeData(TypeInfo)^.RecSize);
RecordInitialize(result, TypeInfo);
end;
function NewRec2(TypeInfo: pointer): pointer;
var
len: integer;
begin
len := GetTypeData(TypeInfo)^.RecSize;
GetMem(result, len);
FillChar(result^, len, 0);
end;
procedure NewDispose(Rec: pointer; TypeInfo: Pointer);
begin
RecordClear(Rec, TypeInfo);
FreeMem(Rec);
end;
SynCommons.pas
单元,并为您的项目定义了DOPATCHTRTL
条件,那么您将拥有RTL FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance
低级函数的进程内补丁,该补丁将使用优化的汇编和SSE2操作码(如果可用)。
GetMem
并清零内存通常会更快。对于非托管类型,没有任何好处。 - David Heffernan