为什么这个字符串的引用计数为4?(德尔福2007)

dum*_*uch 16 delphi string reference-counting delphi-2007 string-interning

这是一个非常特殊的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;
Run Code Online (Sandbox Code Playgroud)

没有什么真正的花哨:FList是一个排序的TStringList,所以所有的代码都在查找列表中的字符串,如果它已经在那里它返回现有的字符串.如果它尚未出现在列表中,它将首先调用UniqueString以确保引用计数为1,然后将其添加到列表中.(我检查了结果的引用计数,并且在'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;
Run Code Online (Sandbox Code Playgroud)

这会将字符串'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;
Run Code Online (Sandbox Code Playgroud)

在调试器中,可以将其评估为:

plongword(integer(pointer(s2))-8)^
Run Code Online (Sandbox Code Playgroud)

只是为了添加Serg的答案(似乎100%正确):

如果我更换

s2 := FPool.Intern(s2);
Run Code Online (Sandbox Code Playgroud)

s3 := FPool.Intern(s2);
s2 := '';
Run Code Online (Sandbox Code Playgroud)

然后检查s3(和s1)的引用计数,它是预期的3.这只是因为将FPool.Intern(s2)的结果再次分配给s2(s2是两者,一个参数和函数结果的目标)导致了这种现象.Delphi引入了一个隐藏的字符串变量来分配结果.

另外,如果我将函数更改为过程:

procedure TStringPool.Intern(var _s: string);
Run Code Online (Sandbox Code Playgroud)

由于不需要隐藏变量,因此引用计数为3.


如果有人对这个TStringPool实现感兴趣:它是MPL下的开源,并且作为dzlib的一部分提供,而dzlib又是dzchart的一部分:

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

但如上所述:这并不完全是火箭科学.;-)

klu*_*udg 14

测试一下:

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;
Run Code Online (Sandbox Code Playgroud)

如果编写s1:= Add(s1),编译器会创建一个隐藏的本地字符串变量,并且此变量负责增加引用计数.你不应该为此烦恼.