Domanda

I have an advanced record with a dynamic array field. The record has a class operator for concatenation of a record and a byte. Also an Add method, adding a byte.

For what I'm about to use the record, the reference count of the dynamic array field is of importance. When running the two test procedures below, you can see that the concatenation results in a reference count of 2 while the add method results in a reference count of 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.

The compiler takes care of the extra reference count when the record variable goes out of scope, so no problem really at this point.

But can this behavior be explained? Is it an undocumented implementation detail? Is there a way to avoid the extra count?

È stato utile?

Soluzione

Let's take a look at this function:

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

The compiler actually implements it like this:

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

The compiler introduces a temporary local to store the result of r1 + 65. There's a very good reason for that. If it did not, where would it write the result of your addition operator? Since the ultimate destination is r1, if your addition operator writes directly to r1 it is modifying its input variable.

There is no way to stop the compiler generating this temporary local.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top