记录中的动态数组引用计数

时间:2013-06-10 20:58:29

标签: delphi record dynamic-arrays reference-counting

我有一个带有动态数组字段的advanced record。 该记录具有class operator用于连接记录和字节。另外一个Add方法,添加一个字节。

对于我即将使用该记录的内容,动态数组字段的引用计数非常重要。运行下面的两个测试过程时,您可以看到连接导致引用计数为2,而add方法导致引用计数为1.

program TestReferenceCount;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

Type
  TRec = record
    class operator Add(const a: TRec; b: Byte): TRec;
  private type
    PDynArrayRec = ^TDynArrayRec;
    TDynArrayRec = packed record
      {$IFDEF CPUX64}
      _Padding: LongInt; // Make 16 byte align for payload..
      {$ENDIF}
      RefCnt: LongInt;
      Length: NativeInt;
    end;
  private
    FArr: TBytes;
    function GetRefCnt: Integer;
  public
    procedure Add(b : Byte);
    property RefCnt: Integer read GetRefCnt;
  end;

procedure TRec.Add(b : Byte);
var
  prevLen: Integer;
begin
  prevLen := System.Length(Self.FArr);
  SetLength(Self.FArr, prevLen + 1);
  Self.FArr[prevLen] := b;
end;

class operator TRec.Add(const a: TRec; b: Byte): TRec;
var
  aLen: Integer;
begin
  aLen := System.Length(a.FArr);
  SetLength(Result.FArr, aLen + 1);
  System.Move(a.FArr[0], Result.FArr[0], aLen);
  Result.FArr[aLen] := b;
end;

function TRec.GetRefCnt: Integer;
begin
  if Assigned(FArr) then
    Result := PDynArrayRec(NativeInt(FArr) - SizeOf(TDynArrayRec)).RefCnt
  else
    Result := 0;
end;

procedure TestConcatenation;
var
  r1 : TRec;
begin
  WriteLn('RC:', r1.RefCnt);  // <-- Writes 0
  r1 := r1 + 65;
  WriteLn('RC:', r1.RefCnt);  // <-- Writes 2
end;

procedure TestAdd;
var
  r1 : TRec;
begin
  WriteLn('RC:', r1.RefCnt); // <-- Writes 0
  r1.Add(65);
  WriteLn('RC:', r1.RefCnt); // <-- Writes 1
end;

begin
  TestConcatenation;
  TestAdd;
  ReadLn;
end.

当记录变量超出范围时,编译器会处理额外的引用计数,所以此时没有问题。

但是可以解释这种行为吗?它是一个未记录的实现细节吗?有没有办法避免额外的计数?

1 个答案:

答案 0 :(得分:2)

让我们来看看这个函数:

procedure TestConcatenation;
var
  r1 : TRec;
begin
  r1 := r1 + 65;
end; 

编译器实际上是这样实现的:

procedure TestConcatenation;
var
  r1 : TRec;
  tmp : TRec;
begin
  tmp := r1 + 65;
  r1 := tmp;
end; 

编译器引入了一个临时本地来存储r1 + 65的结果。这是有充分理由的。如果没有,那么它会在哪里写出加法运算符的结果?由于最终目标是r1,如果您的加法运算符直接写入r1,它正在修改其输入变量。

无法停止编译器生成此临时本地。