What does New and Dispose internally?

I was wondering what happens inside when New and Disposed is called. Delphi help provides a reasonable explanation, but what should I do if a person writes one of their own New and Dispose? What methods are called internally for two or is it all an assembly?

I do not want to write my own New and Dispose. I'm just really interested in how internal methods work.

+5
source share
2 answers

New does the following:

  • Allocate memory for the new object with a call to GetMem .
  • Initialize any managed fields, such as strings, interfaces, dynamic arrays, etc.

Dispose cancels this:

  • Complete managed fields.
  • FreeMem memory by calling FreeMem .

Note that both New and Dispose are internal functions . This means that the compiler has additional knowledge about them and can change the way they are implemented depending on the type in question.

For example, if a type has no managed fields, then New optimized into a simple GetMem call. If the type has managed fields, then New is implemented with a call to System._New , which performs the above actions.

The implementation of Dispose almost the same. A simple call to FreeMem for non-managed types and a call to System._Dispose otherwise.

Now System._New is executed as follows:

 function _New(Size: NativeInt; TypeInfo: Pointer): Pointer; begin GetMem(Result, Size); if Result <> nil then _Initialize(Result, TypeInfo); end; 

Please note that I just showed the PUREPASCAL option for simplicity. Calling GetMem quite simple. The call to System._Initialize much more active. This 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 can contain elements that are themselves types of structure. 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 very similar to System._Initialize , except that it completes the managed types, rather than initializing them.

This has long been a bit of a performance-sensitive Delphi user error that System._Initialize and System._Finalize implement this way on top of information like System._Finalize . Types are known at compile time, and the compiler can be written to enable initialization and termination, which will improve performance.

+9
source

How to write your own functions New() and Dispose() with your own initialization / completion of the record:

 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; 

Firstly, there is a low-level trick for calling hidden internal functions that we need.

Then I suggest two ways to initialize the record using System.@InitializeRecord and the other is FillChar . Also note that if you select your records in a dynamic array, the elements will be initialized / finalized automatically. And initialization will not call Initialize() , but FillChar to fill the memory with zeros.

It is a pity that we were not able to define global functions / procedures with common parameters, otherwise we could get rid of the TypeInfo() parameter.

A few years ago I re-wrote the RTL part of the initialization / completion of the recording for faster execution. Note that TObject will call FinalizeRecord , so it is part of the RTL that benefits from optimization. The compiler should emit code instead of using RTTI - but at least RTL should be optimized a bit more.

If you use our open source module SynCommons.pas and define the DOPATCHTRTL condition for your project, you will have in the process a patch of low-level RTL functions FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance that will use optimized asssembly and SSE2 operation codes if they are available.

+4
source

Source: https://habr.com/ru/post/1240846/


All Articles