Это очень специфический вопрос 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, а затем добавить его в список. (Я проверил счетчик ссылок для результата, и он добавляется три раза после добавления "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;
Это добавляет строку "hallo" в пул строк дважды и проверяет количество ссылок на строку, а также то, что s1 и s2 действительно указывают на один и тот же дескриптор строки.
Каждый CheckEquals работает так, как ожидалось, но последний. Он терпит неудачу с ожидаемой ошибкой: < 3 > но был: < 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)^
Просто добавьте ответ от Serg (который, кажется, на 100% правильный):
Если я заменил
s2 := FPool.Intern(s2);
с
s3 := FPool.Intern(s2);
s2 := '';
а затем проверить счетчик ссылок s3 (и s1), как и ожидалось. Это просто из-за назначения результата 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
Но, как сказано выше: это не совсем ракетостроение.; -)