-1

我有一个循环更新一维数组(Arr2)中的值,然后将数组添加到列表(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;

最初我在 for 循环开始时没有调用 setlength,在这种情况下,适当数量的项目被添加到 resultPnts,但它们都是相同的(就像分配给 Arr2.Pnts[0] 的逻辑一样并且 Arr2.Pnts[1] 在循环的每次迭代中都没有被调用)

我已经通过添加 setlength 调用解决了这个问题,但我真的不明白为什么这是必要的。我很想了解这里发生了什么,以便将来可以更可靠地避免此类问题。

谁能向我解释为什么没有循环中的 setlength 代码不能按预期工作?

4

1 回答 1

2

动态数组是引用计数的。

如果没有SetLength()循环内部,您将添加变量的多个副本,但它们都引用内存中的同一个物理数组,您在每次循环迭代时都对其进行修改。这就是为什么所有条目最终都具有最后一次循环迭代分配的相同数组值的原因。Arr2resultPnts

  • 在进入循环之前,Arr2.Pnts指向一个带有refcount=1from 的数组SetLength()
  • Arr2第一次循环迭代修改现有数组的内容,然后添加to的副本,resultPnts将数组递增refcount到 2。
  • Arr2第二次循环迭代修改现有数组的内容,然后添加to的副本,resultPnts将数组的增量refcount增加到 3。
  • 以此类推,直到循环结束。
  • PathEntToPntArr()退出时,仅Arr2.Pnts清除 in 的引用,递减数组的refcount,但数组仍然具有来自 in 的所有条目的活动引用resultPnts。稍后清除所有这些引用时,将释放该数组。

SetLength()有一个副作用,refcount=1如果新大小 > 0,它会强制现有动态数组,即使数组的现有大小是相同的值。如果数组有refcount=1SetLength()将就地修改数组,否则它将递减数组refcount,然后用 分配一个新数组refcount=1

因此,SetLength()在循环内部,您的resultPnts每个条目都会以分配给它们的唯一数组结束。

  • 在进入循环之前,Arr2.Pnts指向一个带有refcount=1from 的数组SetLength()
  • 第一次循环迭代调用SetLength(),保持当前数组不变,然后修改该数组的内容,然后添加Arr2to的副本resultPnts,将该数组的值refcount增加到 2。
  • 第二次循环迭代调用SetLength(),将当前数组递减refcount到 1 并创建一个新数组refcount=1,然后修改该新数组的内容,然后添加Arr2to的副本resultPnts,将该数组递增refcount到 2。
  • 以此类推,直到循环结束。
  • PathEntToPntArr()退出时,仅Arr2.Pnts清除 in 的引用,递减refcount最后创建的数组的 。每个数组都resultPnts将被单独释放,因为它们的条目稍后会被清除。

XE7+ 中的 RTL 具有DynArrayUnique()与您的调用相同目的的公共函数SetLength(),例如:

procedure PathEntToPntArr (ent : entity; var resultPnts : tPaths);
var
  Arr1, Arr2 : EntPntArr;
  j : integer;
begin
  ...                
  SetLength(Arr2.Pnts, 2);
  ...
  for j := 0 to length(Arr1.Pnts)-1-ord(Arr1.closed) do begin
    DynArrayUnique(Pointer(Arr2.Pnts), TypeInfo(point)); // <--
    ...
    resultPnts.Add(Arr2); 
  end;
  ...
end;

或者,您可以System.Copy()改用:

procedure PathEntToPntArr (ent : entity; var resultPnts : tPaths);
var
  Arr1, Arr2 : EntPntArr;
  j : integer;
begin
  ...                
  SetLength(Arr2.Pnts, 2);
  ...
  for j := 0 to length(Arr1.Pnts)-1-ord(Arr1.closed) do begin
    Arr2.Pnts := Copy(Arr2.Pnts{0, Length(Arr2.Pnts)}); // <--
    ...
    resultPnts.Add(Arr2); 
  end;
  ...
end;
于 2021-07-01T20:57:42.060 回答