Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interface field in record

Can I rely on the fact, that an interface field in a record is always initialized to nil?

TMyRec = record  
  FGuard : IInterface;
  FObject : TObject;
  procedure CheckCreated;
end;

This would allow me to write:

procedure TMyCheck.CheckCreated;
begin
if (FGuard = nil) then
  begin
  FObject := TObject.Create;
  FGuard := TGuard.Create (FObject);
  end;
end;

(for automatic lifetime management)

I know that interface fields are initialized to nil but is that also true when contained in a record?

like image 219
jpfollenius Avatar asked Nov 15 '25 22:11

jpfollenius


1 Answers

Yes you can rely on that.

All reference-counted variables:

  • Strings;
  • Dynamic arrays;
  • Variants;
  • Interfaces;
  • Nested records containing those kind of variables.

are initialized to nil when a record is allocated, if you use New or a dynamic array - even locally on the stack. Of course, if you use a plain GetMem or work with pointers, you'll have to initialize it by yourself (e.g. using a FillChar).

If you are curious, there is an hidden call to the following procedure of System.pas:

procedure _InitializeRecord(p: Pointer; typeInfo: Pointer);

This will fill all the reference-counted variables memory to 0, but won't set the other members of the record. In fact, in a class instance, the whole field memory is initialized with 0, including all members - for a record, the initialization is only for reference-counted types.

Note that in some cases, I've found out that this initialization was not properly generated if you use the object type instead of record - at least under Delphi 2009-2010. So if your code has some object type declaration, you may better switch to record (and loose inheritance), or explicitly call FillChar.

If you are curious, here is an optimized version I wrote in asm - available in our enhanced RTL.

procedure _InitializeRecord(p: Pointer; typeInfo: Pointer);
// this procedure is called at most object creation -> optimization rocks here!
asm
        { ->    EAX pointer to record to be initialized }
        {       EDX pointer to type info                }
        MOVZX   ECX,[EDX+1]                  { type name length }
        PUSH    EBX
        PUSH    ESI
        PUSH    EDI
        MOV     EBX,EAX                     // PIC safe. See comment above
        LEA     ESI,[EDX+ECX+2+8]           { address of destructable fields }
        MOV     EDI,[EDX+ECX+2+4]           { number of destructable fields }
@@loop:
        mov edx,[esi]    // type info
        mov eax,[esi+4]
        mov edx,[edx]
        add esi,8
        add eax,ebx      // data to be initialized
        movzx ecx,[edx]  // data type
        cmp ecx,tkLString
        je @@LString
        jb @@err
        cmp ecx,tkDynArray
        je @@DynArray
        ja @@err
        jmp dword ptr [ecx*4+@@Tab-tkWString*4]
        nop; nop; nop // align @@Tab
@@Tab:  dd @@WString,@@Variant,@@Array,@@Record
        dd @@Interface,@@err
@@LString:
@@WString:
@@Interface:
@@DynArray: // zero 4 bytes in EAX
        dec edi
        mov dword ptr [eax],0
        jg @@loop
        POP     EDI
        POP     ESI
        POP     EBX
        RET
@@Variant: // zero 16 bytes in EAX
        xor ecx,ecx
        dec edi
        mov [eax],ecx
        mov [eax+4],ecx
        mov [eax+8],ecx
        mov [eax+12],ecx
        jg @@loop
        jmp @@exit
@@err:
        MOV     AL,reInvalidPtr
        POP     EDI
        POP     ESI
        POP     EBX
        JMP     Error
@@Array:
@@Record: // rarely called in practice
        mov ecx,1
        call _InitializeArray
        dec edi
        jg @@loop
@@exit:
        POP     EDI
        POP     ESI
        POP     EBX
end;
like image 134
Arnaud Bouchez Avatar answered Nov 18 '25 21:11

Arnaud Bouchez