我有一个带有动态数组字段的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.
当记录变量超出范围时,编译器会处理额外的引用计数,所以此时没有问题。
但是可以解释这种行为吗?它是一个未记录的实现细节吗?有没有办法避免额外的计数?
答案 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
,它正在修改其输入变量。
无法停止编译器生成此临时本地。