Why does this one PAnsiChar get chopped when converted to AnsiString? - delphi

Please consider the following program:
program SO41175184;
{$APPTYPE CONSOLE}
uses
SysUtils;
function Int9999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString(IntToStr(9999)));
end;
function Int99999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString(IntToStr(99999)));
end;
function Int999999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString(IntToStr(999999)));
end;
function Str9999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString('9999'));
end;
function Str99999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString('99999'));
end;
function Str999999: PAnsiChar;
begin
Result := PAnsiChar(AnsiString('999999'));
end;
begin
WriteLn(Int9999); // '9999'
WriteLn(Int99999); // '99999'
WriteLn(Int999999); // '999999'
WriteLn(string(AnsiString(Str9999))); // '9999'
WriteLn(string(AnsiString(Str99999))); // '99999'
WriteLn(string(AnsiString(Str999999))); // '999999'
WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(9999)))))); // '9999'
WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(99999)))))); // '99999'
WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(999999)))))); // '999999'
WriteLn(string(AnsiString(Int9999))); // '9999'
WriteLn(string(AnsiString(Int99999))); // '9999' <----- ?!
WriteLn(string(AnsiString(Int999999))); // '999999'
ReadLn;
end.
Only in one of these cases does the string lose a single character, in Delphi 2010 and Delphi XE3 both. With FPC the same program works correctly. Switching to PChar also makes the problem disappear.
I suppose it has something to do with memory management, but I don't have enough of a clue where to look to do a meaningful investigation. Could anyone clarify?

Dynamically created strings are reference counted and deallocated when no references remain.
Result := PAnsiChar(AnsiString(IntToStr(99999)));
causes a temporary AnsiString to be created, its address taken via the cast to PAnsiChar, and then the temporary string deallocated†. The resulting pointer points to now-unclaimed memory that may be overwritten for pretty much any reason, including during the allocations of more strings.
Neither Delphi nor FPC clears memory by default during deallocations, so if the memory hasn't been re-used yet, you may get lucky when reading what used to be there. Or, as you saw, you may not.
When returning PAnsiChar like this, you need an agreement between caller and callee on memory management. You need to make sure you do not free the memory early, and you need to make sure your callers know how to free the memory afterwards.
† Remy Lebeau points out that this deallocation happens when the procedure or function returns. If there is another statement after the assignment to Result, the string will still be available. This is normally correct, but there are also cases where it the temporary string gets deallocated before the return, for example when you create temporary strings in a loop. I would not recommend using temporary objects after the statement that creates them concludes, even in cases where it is valid, because it makes it too hard to verify whether the code is correct. For those cases, just use an explicit variable.

Related

How writing to MemoryStream has changed in Delphi 10.2?

I am porting my code from 10.1 to 10.2 and this gives me error:
procedure TForm4.FormCreate(Sender: TObject);
const
CFourBytes: array[0..3] of Byte = (1, 2, 3, 4);
var
LStream: TMemoryStream;
LBuffer: array of Byte;
begin
SetLength(LBuffer, 4);
LStream := TMemoryStream.Create;
LStream.Write(#CFourBytes[0], 4); // E2036 Variable required
LStream.Position := 0;
LStream.ReadData(#LBuffer[0], 4);
end;
I had to change offending line to LStream.Write(CFourBytes[0], 4);
What has changed? Have I been doing it wrong for the whole time?
The code in your question did compile in older versions, but it should not have done. The behaviour seen in 10.2 is correct.
What happens in old versions is very strange. The compiler selects this overload in TStream:
function Write(const Buffer: TBytes; Count: Longint): Longint; overload;
That is especially egregious because what has been passed to this method is the address of the static array CFourBytes. Which is categorically not a TBytes object.
Now it just so happens that a TBytes variable is the address of the first element of the array. And nothing in the TBytes override for TMemoryStream.Write refers to Length() of that bogus TBytes object. So your code happens to work as intended. This is very clearly a compiler error that has been fixed.
Your code has always been broken, you have just, up until now, been getting away with it by fortune. You should fix your code. Like this:
LStream := TMemoryStream.Create;
try
LStream.WriteBuffer(CFourBytes, SizeOf(CFourBytes));
SetLength(LBuffer, LStream.Size);
LStream.Position := 0;
LStream.ReadBuffer(LBuffer[0], LStream.Size);
finally
LStream.Free;
end;
Note that I am using WriteBuffer and ReadBuffer instead of Write and Read. These are the preferred methods to use with TStream. The reason being that they perform error checking and raise exceptions in case of errors, unlike Write and Read.
Perhaps nothing has been changed.
TStream.Write/Read methods always used untyped const/var parameter const Buffer (help) and using address of variable is wrong (because method (to be exact - compiler) finds address of variable itself).
Probably you accidentally confuse these methods with Read/WriteData ones that use typed parameter and one of overloaded versions gets Pointer type argument.
Here ReadData implementation dereferences this pointer and uses Read internally (Read in its turn calls Move and the last routine gets address of buffer again :) )

Disposing pointers to complex records

I have list of pointers to some complex records. Sometimes when I try disposing them I get invalid pointer operation error. I'm not really sure if I'm creating and disposing them properly.
The record looks like this:
type
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80; // that's array[0..80] of WideChar
pFullPath: PVeryLongPath; // this is pointer to array of WideChar
pNext: PFILEDATA; // this is pointer to the next TFILEDATA record
end;
As I understand when I want a pointer to such record I need to initialize the pointer and the dynamic arrays like this:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
New(Result^.pFullPath);
end;
Now to dispose of series of these records I wrote this:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData^.pNext <> nil do begin
pNextData := pData^.pNext; // Save pointer to the next record
Finalize(pData^.pFullPath); // Free dynamic array
Dispose(pData); // Free the record
pData := pNextData;
end;
Finalize(pData^.pFullPath);
Dispose(pData);
pData := nil;
end;
When I run my program in the debug mode (F9) in the Delphi 2010 IDE something weird happens. When I step trough DisposeData code with F8 it appears that program skips Finalize(pData^.pFullPath) line and jumps to Dispose(pData). Is this normal? Also when Dispose(pData) is executed the Local variables window that displays contents of the pointers does not change. Does this mean that dispose fails?
Edit:
PVeryLongPath is:
type
TVeryLongPath = array of WideChar;
PVeryLongPath = ^TVeryLongPath;
Edit2
So I create 2 TFILEDATA records then I dispose them. Then I create the same 2 records again. For some reason this time pNext in the second record is not nil. It points to the 1st record. Disposing this weird thing gets invalid pointer operation error.
Randomly I have inserted pData^.pNext := nil in the DisposeData procedure.
Now the code looks like this:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
pData^.pNext := nil; // <----
Dispose(pData^.pFullPath);
Dispose(pData);
pData := pNextData;
end;
end;
The error is gone.
I'll try to change PVeryLongPath into TVeryLongPath.
First, if you free something, the contents of pointers to it do not change. That is why you don't see a change in the local variables display.
EDIT: declare pFullPath as TVeryLongPath. This is a reference type already, and you should not use a pointer to such a type. New() doesn't do what you think it does, in such a case.
It would probably be better if you declared it as UnicodeString, or if your Delphi doesn't have that, WideString.
If pFullPath is declared as a dynamic "array of WideChar", then you should not use New() on it. For dynamic arrays, use SetLength() and nothing else. Dispose() will properly dispose of all items in your record, so just do:
New(Result);
SetLength(Result^.pFullPath, size_you_need);
and later:
Dispose(pData);
In normal code, you should never have to call Finalize(). This is all taken care of by Dispose, as long as you pass a pointer of the correct type to Dispose().
FWIW, I would recommend this and this article of mine.
The fact that you accepted Serg's answer indicates that there is something wrong with your node creation code. Your comment to that answer confirms that.
I'm adding this as a new answer because the edits to the question significantly change it.
Linked list code should look like this:
var
Head: PNode=nil;
//this may be a global variable, or better, a field in a class,
//in which case it would be initialized to nil on creation
function AddNode(var Head: PNode): PNode;
begin
New(Result);
Result.Next := Head;
Head := Result;
end;
Notice that we are adding the node to the head of the list. We don't need to initialize Next to nil anywhere because we always assign another node pointer to Next. That rule is important.
I've written this as a function which returns the new node. Since the new node is always added at the head this is somewhat redundant. Because you can ignore function return values it doesn't really do any harm.
Sometimes you may want to initialize the contents of the node when you add new nodes. For example:
function AddNode(var Head: PNode; const Caption: string): PNode;
begin
New(Result);
Result.Caption := Caption;
Result.Next := Head;
Head := Result;
end;
I much prefer this approach. Always make sure that your fields are initialized. If zero initialization is fine for you then you can use AllocMem to create your node.
Here's a more concrete example of using such a method:
type
PNode = ^TNode;
TNode = record
Caption: string;
Next: PNode;
end;
procedure PopulateList(Items: TStrings);
var
Item: string;
begin
for Item in Items do
AddNode(Head, Item);
end;
To destroy the list the code runs like this:
procedure DestroyList(var Head: PNode);
var
Next: PNode;
begin
while Assigned(Head) do begin
Next := Head.Next;
Dispose(Head);
Head := Next;
end;
end;
You can clearly see that this method can only return when Head is nil.
If you encapsulate your linked list in a class then you can make the head pointer a member of the class and avoid the need to pass it around.
The main point I would like to make is that manual memory allocation code is delicate. It is easy to make little mistakes in the details. In situations like that it pays to put the delicate code in helper functions or methods so you only need to write it once. Linked lists are a great example of a problem that loves to be solved with generics. You can write the memory management code once and re-use it for all sorts of different node types.
I recommend that you avoid using a dynamic array of WideChar which is not at all convenient to work with. Instead use string if you have Delphi 2009 or later, or WideString for earlier Delphi versions. Both of these are dynamic string types with WideChar elements. You can assign to them and Delphi deals with all the allocation.
So, assuming that you now have the following record:
TFILEDATA = record
Description80: TFileType80;
pFullPath: WideString;
pNext: PFILEDATA;
end;
you can simplify things considerably.
function GimmeNewData(): PFILEDATA;
begin
New(Result);
end;
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
Dispose(pData);
pData := pNextData;
end;
end;
You should also initialize pNext field to nil - without it you will finally get access violation. Taking into account what was already said in the previous answers, you can change your code as
type
TFileType80 = array[0..80] of WideChar;
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80;
FullPath: WideString;
pNext: PFILEDATA;
end;
function GimmeNewData: PFILEDATA;
begin
New(Result);
Result^.pNext:= nil;
end;
I think most of your problems are caused by the assumption that New() gives you memory that is zeroed out. I'm pretty sure (and I'm also sure someone will correct me if I'm wrong), but Delphi does not guarantee that that is the case. This can be rectified by changing your code to this:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
ZeroMemory(Result, SizeOf(TFILEDATA));
end;
You should always either zero the memory you get allocated for a record, or at least fill all the fields with something else relevant. This behavior is different to objects, which are guaranteed to be zeroed on allocation.
Hope this helps.

How do I stop this Variant memory leak?

I'm using an old script engine that's no longer supported by its creators, and having some trouble with memory leaks. It uses a function written in ASM to call from scripts into Delphi functions, and returns the result as an integer then passes that integer as an untyped parameter to another procedure that translates it into the correct type.
This works fine for most things, but when the return type of the Delphi function was Variant, it leaks memory because the variant is never getting disposed of. Does anyone know how I can take an untyped parameter containing a variant and ensure that it will be disposed of properly? This will probably involve some inline assembly.
procedure ConvertVariant(var input; var output: variant);
begin
output := variant(input);
asm
//what do I put here? Input is still held in EAX at this point.
end;
end;
EDIT: Responding to Rob Kennedy's question in comments:
AnsiString conversion works like this:
procedure VarFromString2(var s : AnsiString; var v : Variant);
begin
v := s;
s := '';
end;
procedure StringToVar(var p; var v : Variant);
begin
asm
call VarFromString2
end;
end;
That works fine and doesn't produce memory leaks. When I try to do the same thing with a variant as the input parameter, and assign the original Null on the second procedure, the memory leaks still happen.
The variants mostly contain strings--the script in question is used to generate XML--and they got there by assigning a Delphi string to a variant in the Delphi function that this script is calling. (Changing the return type of the function wouldn't work in this case.)
Have you tried the same trick as with the string, except that with a Variant, you should put UnAssigned instead of Null to free it, like you did s := ''; for the string.
And by the way, one of the only reasons I can think of that requires to explicitly free the strings, Variants, etc... is when using some ThreadVar.

Initialise string function result?

I've just been debugging a problem with a function that returns a string that has got me worried. I've always assumed that the implicit Result variable for functions that return a string would be empty at the start of the function call, but the following (simplified) code produced an unexpected result:
function TMyObject.GenerateInfo: string;
procedure AppendInfo(const AppendStr: string);
begin
if(Result > '') then
Result := Result + #13;
Result := Result + AppendStr;
end;
begin
if(ACondition) then
AppendInfo('Some Text');
end;
Calling this function multiple times resulted in:
"Some Text"
the first time,
"Some Text"
"Some Text"
the second time,
"Some Text"
"Some Text"
"Some Text"
the third time, etc.
To fix it I had to initialise the Result:
begin
Result := '';
if(ACondition) then
AppendInfo('Some Text');
end;
Is it necessary to initialise a string function result? Why (technically)? Why does the compiler not emit a warning "W1035 Return value of function 'xxx' might be undefined" for string functions? Do I need to go through all my code to make sure a value is set as it is not reliable to expect an empty string from a function if the result is not explicitly set?
I've tested this in a new test application and the result is the same.
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
S := GenerateInfo;
ShowMessage(S); // 5 lines!
end;
This is not a bug, but "feature":
For a string, dynamic array, method
pointer, or variant result, the
effects are the same as if the
function result were declared as an
additional var parameter following the
declared parameters. In other words,
the caller passes an additional 32-bit
pointer that points to a variable in
which to return the function result.
I.e. your
function TMyObject.GenerateInfo: string;
Is really this:
procedure TMyObject.GenerateInfo(var Result: string);
Note "var" prefix (not "out" as you may expect!).
This is SUCH un-intuitive, so it leads to all kind of problems in the code. Code in question - just one example of results of this feature.
See and vote for this request.
We've run into this before, I think maybe as far back as Delphi 6 or 7. Yes, even though the compiler doesn't bother to give you a warning, you do need to initialize your string Result variables, for precisely the reason you ran into. The string variable is getting initialized -- it doesn't start as a garbage reference -- but it doesn't seem to get reinitialized when you expect it to.
As for why it happens... not sure. It's a bug, so it doesn't necessarily need a reason. We only saw it happen when we called the function repeatedly in a loop; if we called it outside a loop, it worked as expected. It looked like the caller was allocating space for the Result variable (and reusing it when it called the same function repeatedly, thus causing the bug), rather than the function allocating its own string (and allocating a new one on each call).
If you were using short strings, then the caller does allocate the buffer -- that's long-standing behavior for large value types. But that doesn't make sense for AnsiString. Maybe the compiler team just forgot to change the semantics when they first implemented long strings in Delphi 2.
This is not a Bug. By definition no variable inside function is initialized, including Result.
So your Result is undefind on first call, and can hold anything. How it is implemented in compiler is irrelevant, and you can have different results in different compilers.
It seems like your function should be simplified like this:
function TMyObject.GenerateInfo: string;
begin
if(ACondition) then
Result := 'Some Text'
else
Result := '';
end;
You typically don't want to use Result on the right side of an assignment in a function.
Anyway, strictly for illustrative purposes, you could also do this, though not recommended:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
begin
S := ''; // Clear before you call
S := GenerateInfo;
end;
ShowMessage(S); // 5 lines!
end;
This looks like a bug in D2007. I just tested it in Delphi 2010 and got the expected behavior. (1 line instead of 5.)
If you think that some automatic management of strings are made to make your life easier, you're only partly right. All such things are also done to make string logic consistent and side-effects free.
In plenty of places there are string passed by reference, passed by value, but all these lines are expecting VALID strings in whose memory-management counter is some valid, not a garbage value. So in order to keep strings valid the only thing for sure is that they should be initialized when they firstly introduced. For example, for any local variable string this is a necessity since this is the place a string is introduced. All other string usage including function(): string (that actually procedure(var Result: string) as Alexander correctly pointed out) just expects valid strings on the stack, not initialized. And validness here comes from the fact that (var Result: string) construction says that "I'm waiting for a valid variable that definetly was introduced before". UPDATE: Because of that the actual contents of Result is unexpected, but due to the same logic, if it's the only call to this function that has a local variable at the left, the emptiness of the string in this case is guaranteed.
Alex's answer is nearly always right and it answers why I was seeing the strange behaviour that I was, but it isn't the whole story.
The following, compiled without optimisation, produces the expected result of sTemp being an empty string. If you swap the function out for the procedure call you get a different result.
There seems to be a different rule for the actual program unit.
Admittedly this is a corner case.
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
function PointlessFunction: string;
begin
end;
procedure PointlessProcedure(var AString: string);
begin
end;
var
sTemp: string;
begin
sTemp := '1234';
sTemp := PointlessFunction;
//PointlessProcedure(sTemp);
WriteLn('Result:' + sTemp);
ReadLn;
end.

Is it memory safe to provide an object as a function result?

Here I provide simple piece of code.
function GetStringList:TStringList;
var i:integer;
begin
Result:=TStringList.Create;
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
end;
procedure ProvideStringList(SL:TStringList);
var i:integer;
Names:TStringList;
begin
Names:=TStringList.Create;
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
Names.Free;
end;
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
SL.Assign(GetStringList);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
And now the question: what will happen to result object in function GetStringList:Tstringlist, which is created, but never freed? (I call 2 times Create and only 1 time Free)
Is it memory safe to provide objects by function or should I use procedures to do this task, where object creation and destroying is simply handled (procedure ProvideStringlist)? I call 2 times Create and 2 times Free.
Or is there another solution?
Thanx in advance
Lyborko
Is it memory safe to provide an object as a function result?
It is possible, but it needs attention from the implementor and the call.
Make it clear for the caller, the he controls the lifetime of the returned object
Make shure you don't have a memory leak when the function fails.
For example:
function CreateBibleNames: TStrings;
begin
Result := TStringList.Create;
try
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Free;
raise;
end;
end;
But in Delphi the most commen pattern for this is:
procedure GetBibleNames(Names: TStrings);
begin
Names.BeginUpdate;
try
//perhaps a Names.Clear here
//but I don't use it often because the other
//way is more flexible for the caller
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
finally
Names.EndUpdate;
end;
end;
so the caller code can look like this:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := CreateBibleNames;
try
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
and the other, more common version:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := TStringList.Create;
try
GetBibleNames(Names);
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
(I have no compiler at the moment, so perhaps there are some errors)
I don't know what you mean by safe, but it is common practice. The caller of the function becomes responsible for freeing the returned object:
var
s : TStringList;
begin
s := GetStringList;
// stuff
s.free;
end;
Memory safety is a stricter variant of type safety. For memory safety, you typically need a precise garbage collector and a type system which prevents certain kinds of typecasts and pointer arithmetic. By this metric, Delphi is not memory safe, whether you write functions returning objects or not.
These are the very kinds of questions I grappled with in my early days of Delphi. I suggest you take your time with it:
write test code with debug output
trace your code step-by-step
try different options and code constructs
and make sure you understand the nuances properly;
The effort will prove a great help in writing robust code.
Some comments on your sample code...
You should get into the habit of always using resource protection in your code, even in simple examples; and especially since your question pertains to memory (resource) protection.
If you name a function GetXXX, then there's no reason for anyone to suspect that it's going to create something, and they're unlikely to protect the resource. So careful naming of methods is extremely important.
Whenever you call a method that creates something, assume it's your responsibility to destroy it.
I noticed some code that would produce Hints from the compiler. I recommend you always eliminate ALL Hints & Warnings in your programs.
At best a Hint just means some arbitrary redundant code (excesses of which make maintenance more difficult). More likely it implies you haven't finished something, or rushed it and haven't finished testing/checking.
A Warning should always be taken seriously. Even though sometimes the compiler's concern is a logical impossibility in the specific situation, the warning may indicate some subtle language nuance that you're not aware of. The code can always be rewritten in a more robust fashion.
I have seen many examples of poor resource protection where there is a compiler warning giving a clue as to the problem. So check them out, it will aid in the learning.
If an exception is raised in a method that returns a new object, care should be taken to ensure there isn't a memory leak as a result.
//function GetStringList:TStringList;
function CreateStringList:TStringList; //Rename method lest it be misinterpreted.
//var i: Integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
begin
Result := TStringList.Create;
try //Protect the memory until this method is done; as it can **only** be done by **this** method!
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Destroy; //Note Destroy is fine because you would not get here if the line: Result := TStringList.Create; failed.
raise; //Very important to re-raise the exception, otherwise caller thinks the method was successful.
end;
end;
A better name for the following would be PopulateStringList or LoadStringList. Again, resource protection is required, but there is a simpler option as well.
procedure ProvideStringList(SL:TStringList);
var //i:integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
Names:TStringList;
begin
Names:=TStringList.Create;
try //ALWAYS protect local resources!
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
finally //Finally is the correct choice here
Names.Free; //Destroy would also be okay.
end;
end;
However; in the above code, creating a temporary stringlist is overkill when you could just add the strings directly to the input object.
Depending on how the input stringlist is used, it is usually advisable to enclose a BeginUpdate/EndUpdate so that the changes can be handled as a batch (for performance reasons). If your method is general purpose, then you have no idea of the origin of the input, so you should definitely take the precaution.
procedure PopulateStringList(SL:TStringList);
begin
SL.BeginUpdate;
try //YES BeginUpdate must be protected like a resource
SL.Add('Adam');
SL.Add('Eva');
SL.Add('Kain');
SL.Add('Abel');
finally
SL.EndUpdate;
end;
end;
our original code below had a memory leak because it called a method to create an object, but did not destroy. However, because the method that created the object was called GetStringList, the error is not immediately obvious.
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
//SL:=TStringList.Create; This is wrong, your GetStringList method creates the object for you.
//SL.Assign(GetStringList);
SL := CreateStringList; //I also used the improved name here.
try //Don't forget resource protection.
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
The only error in your final snippet was the lack of resource protection. The technique used is quite acceptable, but may not be ideally suited to all problems; so it helps to also be familiar with the previous technique.
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
try //Be like a nun (Get in the habit)
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
No, it is not "memory safe". When you create an object, someone has to free it.
Your first example leaks memory:
SL:=TStringList.Create;
SL.Assign(GetStringList); // <-- The return value of GetStringList is
// used, but not freed.
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
The second example works fine, but you don't have to create and free an additional temporary instance (Names)
In general, the second example is slightly better, because it is obvious, who is responsible for the creation and destruction of the list. (The caller) In other situations, a returned object must be freed by the caller or perhaps it's forbidden. You can't tell from the code. If you must do so, it's good practice to name your methods accordingly. (CreateList is better than GetList).
It is the usage that is the leak, not the construct itself.
var sl2 : TStringlist;
sl2:=GetStringList;
sl.assign(sl2);
sl2.free;
is perfectly fine, or easier even,
sl:=getstringlist;
// no assign, thus no copy, one created one freed.
sl.free;
In btn1Click you should do:
var sl2: TStringList;
sl2 := GetStringList:
SL.Assign(sl2);
sl2.Free;
In btn2Click you don't have to create an instance of SL before calling ProvideStringList to not create a memory leak.
I use a combination of both idioms. Pass the object as an optional parameter and if not passed, create the object. And in either case return the object as the function result.
This technique has (1) the flexibility of the creation of the object inside of the called function, and (2) the caller control of the caller passing the object as a parameter. Control in two meanings: control in the real type of the object being used, and control about the moment when to free the object.
This simple piece of code exemplifies this idiom.
function MakeList(aList:TStrings = nil):TStrings;
var s:TStrings;
begin
s:=aList;
if s=nil then
s:=TSTringList.Create;
s.Add('Adam');
s.Add('Eva');
result:=s;
end;
And here are three different ways to use it
simplest usage, for quick and dirty code
var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
when programmer wants to make more explicit ownership and/or use a custom type
sl2:=MakeList(TMyStringsList.create);
when the object is previously created
sl3:=TMyStringList.Create;
....
MakeList(sl3);

Resources