Delphi 如何确定对动态数组的引用数?

Delphi 如何确定对动态数组的引用数?,delphi,memory,dynamic-arrays,Delphi,Memory,Dynamic Arrays,从这个问题()开始,如果我在Delphi中创建了一个动态数组,那么如何访问引用计数 SetLength(a1, 100); a2 := a1; // The reference count for the array pointed to by both // a1 and a2 should be 2. How do I retrieve this? 此外,如果可以访问引用计数,是否也可以手动修改?后一个问题主要是理论性的,而不是实际应用性的(与上面的第一个问题不同)。在谷歌搜索了几遍后,我

从这个问题()开始,如果我在Delphi中创建了一个动态数组,那么如何访问引用计数

SetLength(a1, 100);
a2 := a1;
// The reference count for the array pointed to by both
// a1 and a2 should be 2. How do I retrieve this?

此外,如果可以访问引用计数,是否也可以手动修改?后一个问题主要是理论性的,而不是实际应用性的(与上面的第一个问题不同)。

在谷歌搜索了几遍后,我发现了鲁迪·维尔修斯(Rudy Velthuis)的一篇优秀文章。我强烈建议你读一读。引用部分中的动态数组


在指针指向的地址下面的内存位置,还有两个字段,即分配的元素数和引用计数

如上图所示,如果N是动态数组变量中的地址,则引用计数位于地址N-8,分配的元素数(长度指示符)位于N-4。第一个元素位于地址N


如何访问这些:

SetLength(a1, 100);
a2 := a1;

// Reference Count = 2
refCount := PInteger(NativeUInt(@a1[0]) - SizeOf(NativeInt) - SizeOf(Integer))^;
// Array Length = 100
arrLength := PNativeInt(NativeUInt(@a1[0]) - SizeOf(NativeInt))^;
计算正确偏移量的诀窍是考虑32位和64位平台代码之间的差异。字段大小(字节)如下所示:

          32bit  64bit
RefCount  4      4
Length    4      8

您可以通过检查
系统
单元中的代码来查看如何管理参考计数。以下是来自XE3源的相关部分:

type
  PDynArrayRec = ^TDynArrayRec;
  TDynArrayRec = packed record
  {$IFDEF CPUX64}
    _Padding: LongInt; // Make 16 byte align for payload..
  {$ENDIF}
    RefCnt: LongInt;
    Length: NativeInt;
  end;
....
procedure _DynArrayAddRef(P: Pointer);
begin
  if P <> nil then
    AtomicIncrement(PDynArrayRec(PByte(P) - SizeOf(TDynArrayRec))^.RefCnt);
end;

function _DynArrayRelease(P: Pointer): LongInt;
begin
  Result := AtomicDecrement(PDynArrayRec(PByte(P) - SizeOf(TDynArrayRec))^.RefCnt);
end;
如果要修改引用计数,可以通过公开
系统中的函数来实现:

procedure DynArrayAddRef(P: Pointer);
asm
  JMP    System.@DynArrayAddRef
end;

function DynArrayRelease(P: Pointer): LongInt;
asm
  JMP    System.@DynArrayRelease
end;
请注意,RTL设计人员选择的名称
DynArrayRelease
有点误导,因为它只是减少了引用计数。当计数为零时,它不会释放内存

我不知道你为什么要这么做。请记住,一旦您开始修改引用计数,您就必须对正确进行该操作负全部责任。例如,此程序泄漏:

{$APPTYPE CONSOLE}

var
  a, b: array of Integer;

type
  PDynArrayRec = ^TDynArrayRec;
  TDynArrayRec = packed record
  {$IFDEF CPUX64}
    _Padding: LongInt; // Make 16 byte align for payload..
  {$ENDIF}
    RefCnt: LongInt;
    Length: NativeInt;
  end;

function DynArrayRefCount(P: Pointer): LongInt;
begin
  if P <> nil then
    Result := PDynArrayRec(PByte(P) - SizeOf(TDynArrayRec))^.RefCnt
  else
    Result := 0;
end;

procedure DynArrayAddRef(P: Pointer);
asm
  JMP    System.@DynArrayAddRef
end;

function DynArrayRelease(P: Pointer): LongInt;
asm
  JMP    System.@DynArrayRelease
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  SetLength(a, 1);
  Writeln(DynArrayRefCount(a));
  b := a;
  Writeln(DynArrayRefCount(a));
  DynArrayAddRef(a);
  Writeln(DynArrayRefCount(a));
  a := nil;
  Writeln(DynArrayRefCount(b));
  b := nil;
  Writeln(DynArrayRefCount(b));
end.
{$APPTYPE控制台}
变量
a、 b:整数数组;
类型
PDynArrayRec=^TDynArrayRec;
TDynArrayRec=打包记录
{$IFDEF CPUX64}
_填充:LongInt;//使有效负载的16字节对齐。。
{$ENDIF}
参考文献:LongInt;
长度:Nativent;
结束;
函数DynArrayRefCount(P:指针):LongInt;
开始
如果P为零,那么
结果:=PDynArrayRec(PByte(P)-SizeOf(TDynArrayRec))^.RefCnt
其他的
结果:=0;
结束;
程序DynArrayAddRef(P:指针);
asm
JMP系统。@DynArrayAddRef
结束;
函数DynArrayRelease(P:指针):LongInt;
asm
JMP系统
结束;
开始
ReportMemoryLeaksOnShutdown:=True;
设定长度(a,1);
Writeln(DynArrayRefCount(a));
b:=a;
Writeln(DynArrayRefCount(a));
DynArrayAddRef(a);
Writeln(DynArrayRefCount(a));
a:=零;
Writeln(DynArrayRefCount(b));
b:=零;
Writeln(DynArrayRefCount(b));
结束。
如果调用
DynArrayRelease
将引用计数设为零,那么出于上述原因,您还需要处理该数组。我从未遇到过需要操纵引用计数的问题,强烈建议您避免这样做


最后一点。RTL不通过其公共接口提供此功能。这意味着以上所有内容都是私有实现细节。因此,在未来的版本中可能会发生更改。如果您确实试图读取或修改引用计数,那么您必须认识到这样做依赖于此类实现细节

你为什么要这样做?我不是特别想修改引用计数(没有必要这样做),而是为了调试目的访问它(相关问题,但现在已解决)。@Stefan:谢谢你的添加!但您必须首先测试动态数组变量是否不是nil,以便在内存中分配该变量@GJ:这是一个示例代码,当然它跳过了许多检查以保持概念的可读性。同样,假设变量被正确声明、类型匹配、没有重载函数、包含必需的用途等@GJ。老实说,我不认为克罗姆或其他任何人对此有异议。这个答案中的代码并不意味着可以生产。有时,它通过删除实际生产代码中所需的所有锅炉板错误处理来简化代码。是的,我知道。我也注意到了。我将为64位添加一个颜色稍有不同的类似图形。-1如果引用计数器为1,则直接调用过程DynArrayRelease是错误的,因为变量的内存必须去定位。@GJ我认为我不主张这样做。事实恰恰相反。我警告干扰参考计数的危险。也许你想指出我的答案的具体部分,我建议你这样做?然后我可以更正错误信息。我认为如果将引用计数器减量为0,那么我们还必须调用
systrm。@DynArrayClear
过程。@GJ请您指出我在何处不这样说,或者甚至建议这样做。至少我的回答确实回答了这个问题!但是
DynArrayRelease
过程不应释放变量的内存,这是错误的!
{$APPTYPE CONSOLE}

var
  a, b: array of Integer;

type
  PDynArrayRec = ^TDynArrayRec;
  TDynArrayRec = packed record
  {$IFDEF CPUX64}
    _Padding: LongInt; // Make 16 byte align for payload..
  {$ENDIF}
    RefCnt: LongInt;
    Length: NativeInt;
  end;

function DynArrayRefCount(P: Pointer): LongInt;
begin
  if P <> nil then
    Result := PDynArrayRec(PByte(P) - SizeOf(TDynArrayRec))^.RefCnt
  else
    Result := 0;
end;

procedure DynArrayAddRef(P: Pointer);
asm
  JMP    System.@DynArrayAddRef
end;

function DynArrayRelease(P: Pointer): LongInt;
asm
  JMP    System.@DynArrayRelease
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  SetLength(a, 1);
  Writeln(DynArrayRefCount(a));
  b := a;
  Writeln(DynArrayRefCount(a));
  DynArrayAddRef(a);
  Writeln(DynArrayRefCount(a));
  a := nil;
  Writeln(DynArrayRefCount(b));
  b := nil;
  Writeln(DynArrayRefCount(b));
end.