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.
Related
So i have a procedure, that is getting the list of dom nodes.
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
And a procedure that is using it
procedure TmainForm.getByXpathBtnClick(Sender: TObject);
var
temp:TStringlist;
begin
temp:=TStringlist.Create;
temp.Add('testval');
getNodeListByClass('table_input',temp);
memo1.Lines:=temp;
getNodeListByClass('left iteminfo',temp);
dbgForm.memo1.Lines:=temp;
getNodeListByClass('left',temp);
dbgForm.memo2.Lines:=temp;
freeandnil(temp);
end;
And i really don't understand, why it wouldn't work, result of first procedure is always empty.
I found out, that when the first procedure is executing, "foundNodesList" have the correct list, and setting it to "outputList" is working too, but as soon as its returning to the second procedure (in "temp" list) its just empty.
So its clearing old data from "test" ('testval' what i am writing in the beginning), but not adding the result from the first one.
Could someone point me in the right direction?
The problem is here
outputList := foundNodesList;
FreeAndNil(foundNodesList);
The assignment is a reference assignment. I think that you are expecting the content of foundNodesList to be transferred into outputList. But what happens is that you end up with two variables referring to the same instance.
Your code can be fixed very easily. You do not need a temporary string list, you can simply populate the string list passed into the method.
procedure TmainForm.getNodeListByClass(className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
Note that in the other function when you write
memo1.Lines := temp;
this works a little differently. The Lines property of a TMemo has a property setter that copies the right hand side, rather than taking a reference. Your code that performs assignment to Lines is therefore correct.
You must understand that objects are reference types in Delphi, and that these references are passed by value. So your procedure
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
will never change the outputList of the caller. Indeed, the line
outputList:=foundNodesList;
merely sets the getNodeListByClass procedure's own local variable outputList, which was only a copy of the pointer to the caller's string list. Hence, this copy of the pointer is changed, but the actual object, and the caller's pointer to it, are left unchanged.
Also, even if this had not been the case, your code would have had a bug, because
freeandnil(foundNodesList);
destroys the string list object foundNodesList, and this is the same object that outputList points to at that point. Hence, if the caller would have been able to see the "new" outputList (if it had been a var parameter), it would only see a dangling pointer (memory corruption bug).
What you need is
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
foundNodesList: TStringlist;
begin
foundNodesList := TStringList.Create;
try
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, foundNodesList);
outputList.Assign(foundNodeList);
finally
foundNodeList.Free;
end;
end;
assuming your functions do what I think they do. But this can be simplified to
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
(I don't know if you want to append the list or replace it. You have to adjust the code accordingly.)
Also, notice that you always must protect your objects, using try..finally blocks, for instance. Your code must never leak resources (memory, for instance), not even if an exception is raised!
When I run the following code I get E2555 Cannot capture symbol 'Self'.
type
TLookupTable = record
FData: TArray<TOtherRec>;
procedure ReverseLeftToRight;
end;
procedure TLookupTable.ReverseLeftToRight;
begin
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
FData[i]:= FData[i].ReverseLeftRight;
end); {for i}
end;
How do I fix this?
The problem is that var parameters (including hidden var parameters to Self) do not get captured. However, we do not want to copy the record, that would be useless, because then our method would not work.
The trick is to make the hidden self parameter explicit.
If it's a class, then it's easy (var S:= Self), if not you'll have to declare a pointer to your record.
procedure TLookupTable.ReverseLeftToRight;
type
PLookupTable = ^TLookupTable;
var
S: PLookupTable;
begin
S:= #Self;
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
end;
Now the compiler no longer complains.
(Note that I'm using the implicit syntax for S^.xyz).
Delphi Rio
Using a inline var declaration as shown below, does not work.
//S: PLookupTable;
begin
var S:= #Self; //inline declaration
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
This generates: E2018 Record, object or class type required.
I guess the inline #Self gets resolved to a generic pointer, which is a shame, because there is enough info to infer the correct type for the inline variable.
Asynchronous issues
If you're executing the code using a Async (.NoWait) thread/task, then it might be better to put FData in the local variable. FData, being a dynamic array, is already a pointer (so no copying will take place, just a ref count). And dynamic arrays do not have Copy-on-Write semantics, so the original will get updated.
As is, the Self record might go out of scope whilst the code is running, because the pointer operation S:= #Self does not cause the reference count on FData to increase). This might cause an access violation (or worse).
Taking a reference to FData causes its refcount to go up, meaning it cannot go out of scope prematurely.
type
TSomeRecord = Record
field1: integer;
field2: string;
field3: boolean;
End;
var
SomeRecord: TSomeRecord;
SomeRecAr: array of TSomeRecord;
This is the most basic example of what I have and since I want to reuse SomeRecord (with certain fields remaining empty, without freeing everything some fields would be carried over when I'm reusing SomeRecord, which is obviously undesired) I am looking for a way to free all of the fields at once. I've started out with string[255] and used ZeroMemory(), which was fine until it started leaking memory, that was because I switched to string. I still lack the knowledge to get why, but it appears to be related to it being dynamic. I am using dynamic arrays as well, so I assume that trying ZeroMemory() on anything dynamic would result in leaks. One day wasted figuring that out. I think I solved this by using Finalize() on SomeRecord or SomeRecAr before ZeroMemory(), but I'm not sure if this is the proper approach or just me being stupid.
So the question is: how to free everything at once? does some single procedure exist at all for this that I'm not aware of?
On a different note, alternatively I would be open to suggestions how to implement these records differently to begin with, so I don't need to make complicated attempts at freeing stuff. I've looked into creating records with New() and then getting rid of it Dispose(), but I have no idea what it means when a variable after a call to Dispose() is undefined, instead of nil. In addition, I don't know what's the difference between a variable of a certain type (SomeRecord: TSomeRecord) versus a variable pointing to a type (SomeRecord: ^TSomeRecord). I'm looking into the above issues at the moment, unless someone can explain it quickly, it might take some time.
Assuming you have a Delphi version that supports implementing methods on a record, you could clear a record like this:
type
TSomeRecord = record
field1: integer;
field2: string;
field3: boolean;
procedure Clear;
end;
procedure TSomeRecord.Clear;
begin
Self := Default(TSomeRecord);
end;
If your compiler doesn't support Default then you can do the same quite simply like this:
procedure TSomeRecord.Clear;
const
Default: TSomeRecord=();
begin
Self := Default;
end;
You might prefer to avoid mutating a value type in a method. In which case create a function that returns an empty record value, and use it with the assignment operator:
type
TSomeRecord = record
// fields go here
class function Empty: TSomeRecord; static;
end;
class function TSomeRecord.Empty: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
....
Value := TSomeRecord.Empty;
As an aside, I cannot find any documentation reference for Default(TypeIdentifier). Does anyone know where it can be found?
As for the second part of your question, I see no reason not to continue using records, and allocating them using dynamic arrays. Attempting to manage the lifetime yourself is much more error prone.
Don't make thinks overcomplicated!
Assigning a "default" record is just a loss of CPU power and memory.
When a record is declared within a TClass, it is filled with zero, so initialized. When it is allocated on stack, only reference counted variables are initialized: others kind of variable (like integer or double or booleans or enumerations) are in a random state (probably non zero). When it will be allocated on the heap, getmem will not initialize anything, allocmem will fill all content with zero, and new will initialize only reference-counted members (like on the stack initialization): in all cases, you should use either dispose, either finalize+freemem to release a heap-allocated record.
So about your exact question, your own assumption was right: to reset a record content after use, never use "fillchar" (or "zeromemory") without a previous "finalize". Here is the correct and fastest way:
Finalize(aRecord);
FillChar(aRecord,sizeof(aRecord),0);
Once again, it will be faster than assigning a default record. And in all case, if you use Finalize, even multiple times, it won't leak any memory - 100% money back warranty!
Edit: After looking at the code generated by aRecord := default(TRecordType), the code is well optimized: it is in fact a Finalize + bunch of stosd to emulate FillChar. So even if the syntax is a copy / assignement (:=), it is not implemented as a copy / assignment. My mistake here.
But I still do not like the fact that a := has to be used, where Embarcadero should have better used a record method like aRecord.Clear as syntax, just like DelphiWebScript's dynamic arrays. In fact, this := syntax is the same exact used by C#. Sounds like if Embacardero just mimics the C# syntax everywhere, without finding out that this is weird. What is the point if Delphi is just a follower, and not implement thinks "its way"? People will always prefer the original C# to its ancestor (Delphi has the same father).
The most simply solution I think of will be:
const
EmptySomeRecord: TSomeRecord = ();
begin
SomeRecord := EmptySomeRecord;
But to address all the remaining parts of your question, take these definitions:
type
PSomeRecord = ^TSomeRecord;
TSomeRecord = record
Field1: Integer;
Field2: String;
Field3: Boolean;
end;
TSomeRecords = array of TSomeRecord;
PSomeRecordList = ^TSomeRecordList;
TSomeRecordList = array[0..MaxListSize] of TSomeRecord;
const
EmptySomeRecord: TSomeRecord = ();
Count = 10;
var
SomeRecord: TSomeRecord;
SomeRecords: TSomeRecords;
I: Integer;
P: PSomeRecord;
List: PSomeRecordList;
procedure ClearSomeRecord(var ASomeRecord: TSomeRecord);
begin
ASomeRecord.Field1 := 0;
ASomeRecord.Field2 := '';
ASomeRecord.Field3 := False;
end;
function NewSomeRecord: PSomeRecord;
begin
New(Result);
Result^.Field1 := 0;
Result^.Field2 := '';
Result^.Field3 := False;
end;
And then here some multiple examples on how to operate on them:
begin
// Clearing a typed variable (1):
SomeRecord := EmptySomeRecord;
// Clearing a typed variable (2):
ClearSomeRecord(SomeRecord);
// Initializing and clearing a typed array variabele:
SetLength(SomeRecords, Count);
// Creating a pointer variable:
New(P);
// Clearing a pointer variable:
P^.Field1 := 0;
P^.Field2 := '';
P^.Field3 := False;
// Creating and clearing a pointer variable:
P := NewSomeRecord;
// Releasing a pointer variable:
Dispose(P);
// Creating a pointer array variable:
ReallocMem(List, Count * SizeOf(TSomeRecord));
// Clearing a pointer array variable:
for I := 0 to Count - 1 do
begin
Pointer(List^[I].Field2) := nil;
List^[I].Field1 := 0;
List^[I].Field2 := '';
List^[I].Field3 := False;
end;
// Releasing a pointer array variable:
Finalize(List^[0], Count);
Choose and/or combine as you like.
With SomeRecord: TSomeRecord, SomeRecord will be an instance/variable of type TSomeRecord. With SomeRecord: ^TSomeRecord, SomeRecord will be a pointer to a instance or variable of type TSomeRecord. In the last case, SomeRecord will be a typed pointer. If your application transfer a lot of data between routines or interact with external API, typed pointer are recommended.
new() and dispose() are only used with typed pointers. With typed pointers the compiler doesn't have control/knowlegde of the memory your application is using with this kind of vars. It's up to you to free the memory used by typed pointers.
In the other hand, when you use a normal variables, depending on the use and declaration, the compiler will free memory used by them when it considers they are not necessary anymore. For example:
function SomeStaff();
var
NativeVariable: TSomeRecord;
TypedPointer: ^TSomeRecord;
begin
NaviveVariable.Field1 := 'Hello World';
// With typed pointers, we need to manually
// create the variable before we can use it.
new(TypedPointer);
TypedPointer^.Field1 := 'Hello Word';
// Do your stuff here ...
// ... at end, we need to manually "free"
// the typed pointer variable. Field1 within
// TSomerecord is also released
Dispose(TypedPointer);
// You don't need to do the above for NativeVariable
// as the compiler will free it after this function
// ends. This apply also for native arrays of TSomeRecord.
end;
In the above example, the variable NativeVariable is only used within the SomeStaff function, so the compiler automatically free it when the function ends. This appy for almost most native variables, including arrays and records "fields". Objects are treated differently, but that's for another post.
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);
I want to create a bunch of records (RWell) and to store them in an array in a certain order. Then I want to create a new array (different layout) and rearange the records in it.
Of course, I don't want to duplicate data in RAM so I though that in the second array I should put pointers to the records in the first array. However, I can't do that. Anybody can tell what's wrong with the code below?
Thanks
Type
RWell= record
x: string;
i: integer;
end;
PWell= ^RWell;
RWellArray= Array[0..12, 0..8] of RWell;
procedure TClass1.CreateWells
var
WellMX: RWellArray;
begin
{ should I initialize the WellXM here? }
{ note: WellXM is a static array! }
other stuff
end;
var Wells: array of PWell;
procedure TClass2.AddWell(aWell: RWell);
begin
aWell.Stuff:= stuff; {aWell cannot be readonly because I need to change it here}
SetLength(Wells, Length(Wells)+ 1); { reserve memory }
Wells[High(Wells)]:= #aWell;
end;
procedure TClass3.DisplayWell;
var CurWell: RWell;
begin
CurWell:= CurPrimer.Wells[iCurWell]^; <--- AV here (but in debugger the address is correct)
end;
Solved by Rob K.
In your AddWell function, you're passing the record by value. That means the function gets a copy of the actual parameter. You're storing a pointer to the formal parameter, which is probably just a location on the local stack of the function.
If you want a pointer to a well, then pass a pointer to a well:
procedure AddWell(AWell: PWell);
begin
SetLength(Wells, Length(Wells) + 1);
Wells[High(Wells)] := AWell;
end;
Another option is to pass the record by const value. For records, this means the actual parameter is passed as a reference. A pointer to the formal parameter is also a pointer to the actual parameter:
procedure AddWell(const AWell: RWell);
begin
SetLength(Wells, Length(Wells) + 1);
Wells[High(Wells)] := #AWell;
end;
I wouldn't really rely on that, though. When you want pointers, pass pointers. Some people try to avoid pointers in their code, but they're nothing to be afraid of.