I was wondering about what happens internally when New and Disposed is called. Delphi help gives a reasonable explanation but what would have to be done if a person would write one's own New and Dispose? Which methods are called internally to the two or is it all assembly?
I am not looking to write my own New and Dispose. I am just extremely curious about both methods' internal working.
New
does the following:
GetMem
.Dispose
reverses this:
FreeMem
.Note that both New
and Dispose
are intrinsic functions. This means that the compiler has extra knowledge of them and is able to vary how they are implemented depending on the type in question.
For instance, if the type has no managed fields then New
is optimized into a simple call to GetMem
. If the type has managed fields then New
is implemented with a call to System._New
, which performs the steps described above.
The implementation of Dispose
is much the same. A simple call to FreeMem
for non-managed types, and a call to System._Dispose
otherwise.
Now, System._New
is implemented like this:
function _New(Size: NativeInt; TypeInfo: Pointer): Pointer;
begin
GetMem(Result, Size);
if Result <> nil then
_Initialize(Result, TypeInfo);
end;
Note that I have just shown the PUREPASCAL
variant for simplicity. The call to GetMem
is simple enough. The call to System._Initialize
is a lot more involved. That uses the TypeInfo
argument to find all the managed types contained in the object and initialize them. This is a recursive process because, for example, a record may contain members that are themselves structure types. You can see all the gory details in the RTL source.
As for System._Dispose
, it calls System._Finalize
and then FreeMem
. And System._Finalize
is very similar to System._Initialize
except that it finalizes managed types rather than initializing them.
It's long been something of a bug bear of performance sensitive Delphi users that System._Initialize
and System._Finalize
are implemented this way, on top of runtime type information. The types are known at compile time and the compiler could be written to inline the initialization and finalization which would result in better performance.
It is how to write your own New()
and Dispose()
functions, with your own record initialization/finalization:
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;
First, there is a low-level asm trick to call the hidden intrinsic functions we need.
I then propose two ways of initializing the record, one using System.@InitializeRecord
, the other using FillChar
. Also note that if you allocate your records within a dynamic array, the initialization/finalization of the items would be done automatically. And initialization won't call Initialize()
but FillChar
to fill the memory with zeros.
It is a pity that we could not define global functions/procedures with generic parameters, otherwise we may have get rid of the TypeInfo()
parameter.
Some years ago, I re-wrote the RTL part of record initialization/finalization, for faster execution. Note that TObject
would call FinalizeRecord
, so it is a part of the RTL which gain of being optimized. The compiler should emit the code instead of using the RTTI - but at least the RTL should be optimized a bit more.
If you use our Open Source SynCommons.pas
unit, and define the DOPATCHTRTL
conditional for your project, you will have in-process patch of the RTL FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance
low-level functions, which would use optimized asssembly, and SSE2 opcodes if available.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With