Почему эта строка имеет счетчик ссылок 4?(Delphi 2007) - PullRequest
16 голосов
/ 26 июня 2011

Это очень специфичный для Delphi вопрос (может быть, даже специфичный для Delphi 2007).В настоящее время я пишу простой класс StringPool для интернирования строк.Как хороший маленький кодер, я также добавил модульные тесты и нашел что-то, что сбило меня с толку.

Это код для интернирования:

function TStringPool.Intern(const _s: string): string;
var
  Idx: Integer;
begin
  if FList.Find(_s, Idx) then
    Result := FList[Idx]
  else begin
    Result := _s;
    if FMakeStringsUnique then
      UniqueString(Result);
    FList.Add(Result);
  end;
end;

Ничего особенного: FList - это отсортированный TStringList, так что весь код ищет строку в списке и, если она уже есть, возвращает существующую строку.Если его еще нет в списке, он сначала вызовет UniqueString, чтобы убедиться, что счетчик ссылок равен 1, а затем добавит его в список.(Я проверил счетчик ссылок Результата, и он равен 3 после того, как 'hallo' был добавлен дважды, как и ожидалось.)

Теперь к тестовому коду:

procedure TestStringPool.TestUnique;
var
  s1: string;
  s2: string;
begin
  s1 := FPool.Intern('hallo');
  CheckEquals(2, GetStringReferenceCount(s1));
  s2 := s1;
  CheckEquals(3, GetStringReferenceCount(s1));
  CheckEquals(3, GetStringReferenceCount(s2));
  UniqueString(s2);
  CheckEquals(1, GetStringReferenceCount(s2));
  s2 := FPool.Intern(s2);
  CheckEquals(Integer(Pointer(s1)), Integer(Pointer(s2)));
  CheckEquals(3, GetStringReferenceCount(s2));
end;

Это добавляет строку'Привет' дважды к пулу строк и проверяет счетчик ссылок строки, а также на то, что s1 и s2 действительно указывают на один и тот же дескриптор строки.

Каждый CheckEquals работает, как и ожидалось, но последний.Сбой с ошибкой «ожидаемый: <3>, но был: <4>».

Итак, почему счетчик ссылок 4 здесь?Я бы ожидал 3:

  • s1
  • s2
  • и еще один в StringList

Это Delphi 2007 ипоэтому строки являются AnsiStrings.

О да, функция StringReferenceCount реализована следующим образом:

function GetStringReferenceCount(const _s: AnsiString): integer;
var
  ptr: PLongWord;
begin
  ptr := Pointer(_s);
  if ptr = nil then begin
    // special case: Empty strings are represented by NIL pointers
    Result := MaxInt;
  end else begin
    // The string descriptor contains the following two longwords:
    // Offset -1: Length
    // Offset -2: Reference count
    Dec(Ptr, 2);
    Result := ptr^;
  end;
end;

В отладчике то же самое можно оценить как:

plongword(integer(pointer(s2))-8)^

Просто добавьте к ответу Серга (который кажется на 100% правильным):

Если я заменю

s2 := FPool.Intern(s2);

на

s3 := FPool.Intern(s2);
s2 := '';

и затем проверьте счетчик ссылок s3 (и s1), это 3, как и ожидалось.Это просто из-за повторного присвоения результата FPool.Intern (s2) для s2 (s2 является и параметром, и местом назначения для результата функции), что вызывает это явление.Delphi вводит скрытую строковую переменную для присваивания результата.

Кроме того, если я изменяю функцию на процедуру:

procedure TStringPool.Intern(var _s: string);

количество ссылок равно 3, как и ожидалось, потому что скрытая переменная не являетсяОбязательно.


На случай, если кто-то заинтересован в реализации TStringPool: это открытый исходный код под MPL и доступный как часть dzlib, которая, в свою очередь, является частью dzchart:

https://sourceforge.net/p/dzlib/code/HEAD/tree/dzlib/trunk/src/u_dzStringPool.pas

Но, как сказано выше: это не совсем ракетостроение.; -)

1 Ответ

14 голосов
/ 26 июня 2011

Проверьте это:

function RefCount(const _s: AnsiString): integer;
var
  ptr: PLongWord;
begin
  ptr := Pointer(_s);
  Dec(Ptr, 2);
  Result := ptr^;
end;

function Add(const S: string): string;
begin
  Result:= S;
end;

procedure TForm9.Button1Click(Sender: TObject);
var
  s1: string;
  s2: string;

begin
  s1:= 'Hello';
  UniqueString(s1);
  s2:= s1;
  ShowMessage(Format('%d', [RefCount(s1)]));   // 2
  s2:= Add(s1);
  ShowMessage(Format('%d', [RefCount(s1)]));   // 2
  s1:= Add(s1);
  ShowMessage(Format('%d', [RefCount(s1)]));   // 3
end;

Если вы пишете s1:= Add(s1), компилятор создает скрытую локальную строковую переменную, и эта переменная отвечает за увеличение количества ссылок. Вы не должны беспокоиться об этом.

...