I have a loop that is updating the values in a 1 dimensional array (Arr2) and then adding the array to a list (ResultPnts):
type
point = packed record
case aSInt of
0: (x, y, z: aFloat);
1: (v: array [0 .. 2] of aFloat); { vertex }
end;
MyPntArr = array of point;
EntPntArr = record
enttyp : byte;
zb, zh : double; // store zbase and zheight for 2d lines/arcs
closed : boolean; // set true if first and last points of lines/arcs are equal
Pnts : MyPntArr;
end;
tPaths = TList<EntPntArr>;
procedure PathEntToPntArr (ent : entity; var resultPnts : tPaths);
var
Arr1, Arr2 : EntPntArr;
j : integer;
begin
...
setlength (Arr2.Pnts, 2);
Arr2.closed := false;
Arr2.enttyp := entslb;
for j := 0 to length(Arr1.Pnts)-1-ord(Arr1.closed) do begin
setlength (Arr2.Pnts, 2); // note: I'm not entirely sure why, but without setting length in
// each iteration of the loop, the array points are not updated
// in each subsequent iteration after the first.
Arr2.Pnts[0] := Arr1.Pnts[j];
// add slbthick to Arr1.Pnts[j], with result in the var parameter Arr2.Pnts[1]
AddPnt (Arr1.Pnts[j], slbthick, Arr2.Pnts[1]);
resultPnts.Add(Arr2);
end;
Originally I did not have the setlength call at the start of the for loop, and in this case the appropriate number of items were being added to resultPnts, but they were all the same (as though the logic assigning to Arr2.Pnts[0] and Arr2.Pnts[1] was not being called in every iteration of the loop)
I have fixed the problem by adding the setlength call, but I don't really understand why that is necessary. I would love to understand what is going on here so that I can more reliably avoid this sort of problem in future.
Can anybody explain to me why the code is not working as expected without the setlength in the loop?
Dynamic arrays are reference counted.
Without the
SetLength()inside the loop, you are adding multiple copies of theArr2variable toresultPntsbut they all refer to the same physical array in memory, which you are modifying on each loop iteration. That is why all of the entries end up with the same array values assigned by the last loop iteration.Arr2.Pntspoints to an array withrefcount=1from the initialSetLength().Arr2toresultPnts, incrementing the array'srefcountto 2.Arr2toresultPnts, incrementing the array'srefcountto 3.PathEntToPntArr()exits, only the reference inArr2.Pntsis cleared, decrementing the array'srefcount, but the array still has active references from all of the entries inresultPnts. The array will be freed when all of those references are cleared later.SetLength()has a side effect that it forces an existing dynamic array torefcount=1if the new size is > 0, even if the array's existing size is the same value. If the array hasrefcount=1,SetLength()will modify the array in-place, otherwise it will decrement the array'srefcountand then allocate a new array withrefcount=1.Thus, with
SetLength()inside the loop, yourresultPntsentries will each end up with a unique array assigned to them.Arr2.Pntspoints to an array withrefcount=1from the initialSetLength().SetLength(), leaving the current array intact, then modifies the content of that array, then adds a copy ofArr2toresultPnts, incrementing that array'srefcountto 2.SetLength(), decrementing the current array'srefcountto 1 and creating a new array withrefcount=1, then modifies the content of that new array, then adds a copy ofArr2toresultPnts, incrementing that array'srefcountto 2.PathEntToPntArr()exits, only the reference inArr2.Pntsis cleared, decrementing therefcountof the last array created. Each array inresultPntswill be freed individually as their entries are cleared later.The RTL in XE7+ has a public
DynArrayUnique()function that serves the same purpose as yourSetLength()call, eg:Or, you can use
System.Copy()instead: