6

Given this declaration:

type
  TRec = record
    FNr: integer;
    FName: string;
    constructor Create(ANr: integer);
  end;

and this implementation of the constructor:

constructor TRec.Create(ANr: integer);
begin
  Self := Default(TRec);
  FNr := ANr;
end;

can I be sure the FName of the record is an empty string? The default constructor of a record does not initialize the memory, so I would guess not, but I can't find any information in the help files.

3
  • The question is theoretically interesting and I'm curious to hear an authoritative answer. But I just write the obvious (surely nothing new, but still), that a simple and safe way to achieve empty string after construction is to simply write FName := '' in the record constructor. Commented May 13 at 14:06
  • @MatthiasB: yes, but say you have a huge record with lots of fields, you might want to do something short and clever :-) Commented May 13 at 15:01
  • 3
    Another advantage of Self := Default(TRec) is that it will also initialize any fields you add later on to the record type. If you do FName := ''; ... etc. in the constructor, it's easy to forget to initialize any newly added members of the record type. Commented May 13 at 15:03

1 Answer 1

7

can I be sure the FName of the record is an empty string?

Yes. After

Self := Default(TRec);

the record will consist of only zero bytes, that is, FNr = 0 and FName = ''.

(If Self.FName had somehow pointed to a string heap object before this line, the compiler and RTL would have dealt with that. So this assignment is much smarter than a FillChar(Self, SizeOf(Self), 0). You could also achieve the same effect with a Self := MyDefRec where MyDefRec is any TRec value that you have defined yourself and use as a default value.)

But of course this analysis only applies to situations where you actually do use this constructor you have written.

So if you do

var LRec := TRec.Create(394);

then you are guaranteed that LRec.FName is empty and that LRec.FNr = 394.

If you'd done

var LRec := Default(TRec);

you'd ended up with LRec.FName = '' and LRec.FNr = 0, since the "default" integer value is zero. In this case, your constructor is not used; instead, you simply assign the "default" ("zeroed out") TRec to LRec (which is exactly what the first line of your constructor also does).

But if you do

procedure Test;
var
  LRec: TRec;
begin
  ShowMessage(LRec.FNr.ToString);
  ShowMessage(LRec.FName);
end;

the TRec.Create constructor has never run, nor have you assigned anything to LRec initially.

Now, recall that local variables of non-managed types are not initialised. For instance, a local integer variable will initially have the value that happens to be at that address in memory; it's effectively "random". But local variables of managed types, like strings and dynamic arrays, are of course initialised to nil (empty string or empty array). (So they do not point to any string or dynamic array heap object. Recall that string and dynamic array variables are simply pointers.)

Records are not managed, but can contain managed fields.

So in our example above, the first message box can display any "random" integer (but 0 is quite likely, since your RAM has a lot of zero bytes most of the time).

But the second message will always display an empty string, even though your own constructor has never executed. Again, this is because the string is a managed type and so is initialised to '' (=nil pointer, zero bytes).


If you have a class, like

type
  TMyClass = class
    FRec: TRec;
  end;

and create an instance of that class,

procedure Test2;
begin
  var LMyClass := TMyClass.Create;
  try
    // use LMyClass.FRec
  finally
    LMyClass.Free;
  end;
end;

then FRec will also contain the number 0 and the empty string. This is because fields of classes are always initialized to zero (nil).

Sign up to request clarification or add additional context in comments.

7 Comments

Unfortunately (imho), as of today, record constructors are not subject to RVO, which causes the compiler to always pass a compiler generated temp variable where it called InitializeRecord on if it contains any managed fields to the ctor and after that executes CopyRecord to copy the temp variable to the lhs. This only changes when the record has a class operator Initialize where it instead of InitializeRecord calls that one - but the temp variable remains.
You should be able to do like this, to ensure that a record is always initialized with "emtpy" values? class operator TRec.Initialize(out Dest: TRec); begin Dest:= Default(TRec); end;
If you want a StackOverflow, then yes. Calling Default for a record type that has a custom initializer will call you can guess: the initializer (after additionally calling FinalizeRecord or an existing custom finalizer, which in the context of the Initializer is not even needed because the parameter is out, it was already zero initiated outside of the cmr initializer). If you write a custom initializer you should in fact use FillChar
Yes - it's kinda ridiculous how much work the CPU has to do when you have a bunch of managed and unmanaged fields and you simply want to have it all zero because of how they designed this. The modern FillChar implementation is so fast that the compiler should simply call it and then document that all records that contain at least one managed field or a custom initializer would be completely zero initialized.
What's even more ridiculous: in the case of calling a record ctor the compiler does manual clearing of the managed fields for the temp variable it created (by mov instructions that can get plentiful if you have many fields) but a call to InitializeRecord for the declared variable. If it had properly arranged its variables, it would just be just one FillChar call to initialize all managed local variables. But no - it rather pollutes the prologue with a ton of unnecessary instructions and calls. One could even imagine a compiler flag that extends this behavior to all local variables.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.