2

advanced record動的配列フィールドがあります。レコードにはclass operator、レコードとバイトを連結するための for があります。また、バイトを追加する Add メソッド。

これからレコードを使用するためには、動的配列フィールドの参照カウントが重要です。以下の 2 つのテスト手順を実行すると、連結によって参照カウントが 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.

レコード変数がスコープ外になると、コンパイラが追加の参照カウントを処理するため、現時点では問題はありません。

しかし、この動作は説明できますか? 文書化されていない実装の詳細ですか? 余分なカウントを回避する方法はありますか?

4

1 に答える 1

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、入力変数が変更されます。

この一時ローカルを生成するコンパイラを停止する方法はありません。

于 2013-06-10T21:26:48.183 に答える