2013-06-10 8 views
2

У меня есть advanced record с динамическим полем массива. В записи есть class operator для конкатенации записи и байт. Также добавьте метод, добавив байт.Счетчик динамического массива в записи

Для того, что я собираюсь использовать запись, значение ссылки на поле динамического массива имеет важное значение. При выполнении этих двух процедур испытаний ниже, вы можете увидеть, что результаты конкатенации в счетчик ссылок 2, а результаты метода добавить в счетчик ссылок 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

Говоря о недокументированных деталях реализации, полагаясь на подсчет ссылок динамического массива, чтобы иметь какое-то особое значение, в первую очередь, кажется хрупким. –

+0

@RobKennedy, не уверен, что вы имеете в виду, почему бы вам не доверять счету ссылок динамического массива? Как строка, он удалит себя из кучи, когда счетчик достигнет нуля. –

+0

Конечно, но кто говорит, когда он будет равен нулю? В документации не приводится исчерпывающий список случаев, когда счетчик ссылок будет изменен. Он говорит только, когда счетчик ссылок будет равен 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, он изменяет свою входную переменную.

Невозможно остановить компилятор, создающий этот временный локальный.

+1

'..., если ваш оператор присоединения записывает непосредственно в r1, он изменяет свое входное значение.« Все! Спасибо, спасибо. Я мог видеть код asm, но не мог понять причину. –

Смежные вопросы