16

これは非常に 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' が 2 回追加された後で 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' が文字列プールに 2 回追加され、文字列の参照カウントがチェックされ、s1 と s2 が実際に同じ文字列記述子を指していることも確認されます。

すべての CheckEquals は期待どおりに機能しますが、最後です。「expected: <3> but was: <4>」というエラーで失敗します。

では、なぜここで参照カウントが 4 なのですか? 私は3を期待していたでしょう:

  • s1
  • s2
  • および StringList 内の別の 1 つ

これは 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 の下でオープン ソースであり、dzchart の一部である dzlib の一部として利用できます。

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

しかし、上で述べたように、それは厳密にはロケット科学ではありません。;-)

4

1 に答える 1

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;

あなたが書いs1:= Add(s1)た場合、コンパイラは非表示のローカル文字列変数を作成し、この変数は参照カウントのインクリメントを担当します。気にする必要はありません。

于 2011-06-26T12:34:34.953 に答える