magnus
magnus

Reputation: 4251

How I determine the number of references to a dynamic array?

Following on from this question (Dynamic arrays and memory management in Delphi), if I create a dynamic array in Delphi, how do I access the reference count?

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?

Additionally, if the reference count can be accessed, can it also be modified manually? This latter question is mainly theoretical rather than for use practically (unlike the first question above).

Upvotes: 2

Views: 569

Answers (2)

David Heffernan
David Heffernan

Reputation: 612954

You can see how the reference count is managed by inspecting the code in the System unit. Here are the pertinent parts from the XE3 source:

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;

A dynamic array variable holds a pointer. If the array is empty, then the pointer is nil. Otherwise the pointer contains the address of the first element of the array. Immediately before the first element of the array is stored the metadata for the array. The TDynArrayRec type describes that metadata.

So, if you wish to read the reference count you can use the exact same technique as does the RTL. For instance:

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

If you want to modify the reference count then you can do so by exposing the functions in System:

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

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

Note that the name DynArrayRelease as chosen by the RTL designers is a little mis-leading because it merely reduces the reference count. It does not release memory when the count reaches zero.

I'm not sure why you would want to do this mind you. Bear in mind that once you start modifying the reference count, you have to take full responsibility for getting it right. For instance, this program leaks:

{$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.

And if you make a call to DynArrayRelease that takes the reference count to zero then you would also need to dispose of the array, for reasons discussed above. I've never encountered a problem that would require manipulation of the reference count, and strongly suggest that you avoid doing so.

One final point. The RTL does not offer this functionality through its public interface. Which means that all of the above is private implementation detail. And so is subject to change in a future release. If you do attempt to read or modify the reference count then you must recognise that doing so relies on such implementation detail.

Upvotes: 3

Kromster
Kromster

Reputation: 7397

After some googling, I found an excellent article by Rudy Velthuis. I highly recommend to read it. Quoting dynamic arrays part from http://rvelthuis.de/articles/articles-pointers.html#dynarrays


At the memory location below the address to which the pointer points, there are two more fields, the number of elements allocated, and the reference count.

enter image description here

If, as in the diagram above, N is the address in the dynamic array variable, then the reference count is at address N-8, and the number of allocated elements (the length indicator) at N-4. The first element is at address N.


How to access these:

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))^;

The trick in computing proper offsets is to account for differences between 32bit and 64bit platforms code. Fields size in bytes is as follows:

          32bit  64bit
RefCount  4      4
Length    4      8

Upvotes: 3

Related Questions