Reputation: 4624
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
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