これは非常に 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
しかし、上で述べたように、それは厳密にはロケット科学ではありません。;-)