为什么这个字符串的引用计数为4? (Delphi 2007) [英] Why does this string have a reference count of 4? (Delphi 2007)

查看:181
本文介绍了为什么这个字符串的引用计数为4? (Delphi 2007)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个非常Delphi的具体问题(甚至可能是Delphi 2007具体的)。我正在为一个实习的字符串编写一个简单的StringPool类。作为一个好的小编码器,我还添加了单元测试,发现让我很困惑的东西。



这是实习的代码:

 函数TStringPool.Intern(const _s:string):string; 
var
Idx:Integer;
begin
如果FList.Find(_s,Idx)then
结果:= FList [Idx]
else begin
结果:= _s;
如果FMakeStringsUnique then
UniqueString(Result);
FList.Add(Result);
结束
结束

没有什么真正的花哨:
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(整数(指针(s1)),整数(指针(s2)));
CheckEquals(3,GetStringReferenceCount(s2));
结束

将字符串'hallo'添加到字符串池两次,并检查字符串的引用计数,并且还s1和s2确实指向相同的字符串描述符。



每个CheckEquals按预期工作,但最后一个。它失败,出现预期:<3>,但是是:<4>。



那么为什么引用计数4在这里?我会预期3:




  • s1

  • s2

  • 另一个在StringList



这是Delphi 2007,字符串因此是AnsiStrings。



哦,功能StringReferenceCount被实现为:

  function GetStringReferenceCount(const _s:AnsiString):整数; 
var
ptr:PLongWord;
begin
ptr:=指针(_s);
如果ptr = nil然后开始
//特殊情况:空字符串由NIL指针
表示结果:= MaxInt;
end else begin
//字符串描述符包含以下两个长词:
//偏移-1:长度
//偏移-2:引用计数
Dec (Ptr,2);
结果:= ptr ^;
结束
结束

在调试器中,相同的可以评估为:

  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



解决方案

测试:

 code> function RefCount(const _s:AnsiString):integer; 
var
ptr:PLongWord;
begin
ptr:=指针(_s);
Dec(Ptr,2);
结果:= ptr ^;
结束

函数Add(const S:string):string;
begin
结果:= S;
结束

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

begin
s1:='你好';
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)编译器创建一个隐藏的本地字符串变量,并且该变量负责递增引用计数。你不应该打扰它。


This is a very Delphi specific question (maybe even Delphi 2007 specific). I am currently writing a simple StringPool class for interning strings. As a good little coder I also added unit tests and found something that baffled me.

This is the code for interning:

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;

Nothing really fancy: FList is a TStringList that is sorted, so all the code does is looking up the string in the list and if it is already there it returns the existing string. If it is not yet in the list, it will first call UniqueString to ensure a reference count of 1 and then add it to the list. (I checked the reference count of Result and it is 3 after 'hallo' has been added twice, as expected.)

Now to the testing code:

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;

This adds the string 'hallo' to the string pool twice and checks the string's reference count and also that s1 and s2 indeed point to the same string descriptor.

Every CheckEquals works as expected but the last. It fails with the error "expected: <3> but was: <4>".

So, why is the reference count 4 here? I would have expected 3:

  • s1
  • s2
  • and another one in the StringList

This is Delphi 2007 and the strings are therefore AnsiStrings.

Oh yes, the function StringReferenceCount is implemented as:

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;

In the debugger the same can be evaluated as:

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

Just to add to the answer from Serg (which seems to be 100% correct):

If I replace

s2 := FPool.Intern(s2);

with

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

and then check the reference count of s3 (and s1) it is 3 as expected. It's just because of assigning the result of FPool.Intern(s2) to s2 again (s2 is both, a parameter and the destination for the function result) that causes this phenomenon. Delphi introduces a hidden string variable to assign the result to.

Also, if I change the function to a procedure:

procedure TStringPool.Intern(var _s: string);

the reference count is 3 as expected because no hidden variable is required.


In case anybody is interested in this TStringPool implementation: It's open source under the MPL and available as part of dzlib, which in turn is part of dzchart:

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

But as said above: It's not exactly rocket science. ;-)

解决方案

Test this:

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;

If you write s1:= Add(s1) the compiler creates a hidden local string variable, and this variable is responsible for incrementing ref count. You should not bother about it.

这篇关于为什么这个字符串的引用计数为4? (Delphi 2007)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆