这是一个非常特定于 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,然后将其添加到列表中。(我检查了 Result 的引用计数,在添加了两次 'hallo' 之后它是 3,正如预期的那样。)
现在到测试代码:
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>”。
那么,为什么这里的引用计数是 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)的引用计数,它是 3,如预期的那样。正是因为再次将 FPool.Intern(s2) 的结果赋值给 s2(s2 既是函数结果的参数,又是函数结果的目的地)导致了这种现象。Delphi 引入了一个隐藏的字符串变量来分配结果。
另外,如果我将函数更改为过程:
procedure TStringPool.Intern(var _s: string);
正如预期的那样,引用计数为 3,因为不需要隐藏变量。
如果有人对此 TStringPool 实现感兴趣:它在 MPL 下是开源的,可作为 dzlib 的一部分使用,而 dzlib 又是 dzchart 的一部分:
https://sourceforge.net/p/dzlib/code/HEAD/tree/dzlib/trunk/src/u_dzStringPool.pas
但如上所述:这不完全是火箭科学。;-)