zig
zig

Reputation: 4624

How to initialize var Record parameter

I have the record type:

type
  TIPInfo = record
    IP,
    HostName,
    City,
    Region,
    Country,
    Loc,
    Org: WideString
  end;

Function to return the record data:

function GetPublicIPInfo(var IPInfo: TIPInfo): Boolean;
begin
  // initialize
  FillChar(IPInfo, SizeOf(TIPInfo), 0);

  // populate data
  IPInfo.IP := GetVallue('ip');
  IPInfo.HostName := GetVallue('hostname');
  IPInfo.City := GetVallue('city');
  // etc...

  Result := IsOk;   
end;

The caller:

var
  IPInfo: TIPInfo;

if GetPublicIPInfo(IPInfo) then... // use data

Is it the correct way to initialize the var TIPInfo by calling FillChar or should I set every field to empty string? Should the caller do that?

Also, would it be better to use an out parameter in the case (since the function is not reading the data)?

Upvotes: 3

Views: 2735

Answers (1)

David Heffernan
David Heffernan

Reputation: 612963

Using just FillChar is wrong here. If any of the WideString members are not empty, then you will leak them this way. Instead I suggest the following:

Finalize(IPInfo);
FillChar(IPInfo, SizeOf(TIPInfo), 0);

Or another way is to define a default record as a typed constant:

const
  DefaultIPInfo: TIPInfo = ();

Then you can use simple assignment:

IPInfo := DefaultIPInfo;

In modern versions of Delphi you can instead use this much more readable code:

IPInfo := Default(TIPInfo);

For more on this particular subject, refer to these topics:

Note that the leak in your code is hard to find because WideString variables are implemented as COM BSTR objects, and allocated on the COM heap. Therefore, if you use memory leak detection for the Delphi memory manager the leak will not be detected because it is leaked from a different heap.

In your case, because your record is a managed type, and contains only managed types, then you could use an out parameter to good effect. For managed types, out parameters mean that the compiler will generate code, at the call site, to default initialize the record before passing it in.

Consider the following program:

{$APPTYPE CONSOLE}

type
  TRec = record
    Value: WideString;
  end;

procedure Foo1(var rec: TRec);
begin
end;

procedure Foo2(out rec: TRec);
begin
end;

procedure Main;
var
  rec: TRec;
begin
  rec.Value := 'Foo';
  Foo1(rec);
  Writeln(rec.Value);
  Foo2(rec);
  Writeln(rec.Value);
end;

begin
  Main;
end.

The output is:

Foo

If your record contains a mix of managed and unmanaged types, then the situation is not so good.

{$APPTYPE CONSOLE}

type
  TRec = record
    Value1: WideString;
    Value2: Integer;
  end;

procedure Foo1(var rec: TRec);
begin
end;

procedure Foo2(out rec: TRec);
begin
end;

procedure Main;
var
  rec: TRec;
begin
  rec.Value1 := 'Foo';
  rec.Value2 := 42;
  Foo1(rec);
  Writeln(rec.Value1);
  Writeln(rec.Value2);
  Foo2(rec);
  Writeln(rec.Value1);
  Writeln(rec.Value2);
end;

begin
  Main;
end.

The output is:

Foo
42

42

Only the managed members are default initialized for out parameters. So your best bet is to default initializing the variable yourself, even if it is passed as an out parameter.

More on out parameters can be found here: What's the difference between "var" and "out" parameters?

Upvotes: 5

Related Questions