【问题标题】:Why does this string have a reference count of 4? (Delphi 2007)为什么这个字符串的引用计数为 4? (德尔福 2007)
【发布时间】:2011-09-22 22:03:39
【问题描述】:

这是一个非常特定于 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 都按预期工作,但最后一个。它失败并出现错误“预期: 但原为:”。

那么,为什么这里的引用计数是 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

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

【问题讨论】:

  • 您能否在 TestUnique 结束时检查 S1 的引用计数?我很好奇当时它的引用数是多少。
  • 当然可以使用 debug dcus
  • + 不接受猜测者的废话。
  • @david:我已经尝试过调试 dcus,但这并没有给我任何见解。
  • 您为什么对使字符串独一无二感兴趣?

标签: string delphi delphi-2007 reference-counting string-interning


【解决方案1】:

测试一下:

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),编译器会创建一个隐藏的本地字符串变量,这个变量负责增加引用计数。你不应该为此烦恼。

【讨论】:

  • 所以这基本上是作为 VAR 参数传递的 Delphi 函数结果的产物?我知道接口有类似的效果(有时用于节省 try..finally 构造),但不知何故认为它也不适用于字符串。
  • @dummzeuch - 我认为这是真的。编译器无法在s1:= Add(s1) 中将s1 作为var 传递,因此它创建了一个隐藏变量,将其作为var 传递并将其分配给s1(递增引用计数)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多