string - ¿Por qué esta cadena tiene una cuenta de referencia de 4?(Delphi 2007)
delphi-2007 reference-counting (1)
Prueba esto:
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;
Si escribe s1:= Add(s1)
el compilador crea una variable de cadena local oculta, y esta variable es responsable de incrementar el recuento de ref. No debes preocuparte por ello.
Esta es una pregunta muy específica de Delphi (tal vez incluso específica de Delphi 2007). Actualmente estoy escribiendo una clase StringPool simple para internar cadenas. Como un buen pequeño programador también agregué pruebas unitarias y encontré algo que me desconcertó.
Este es el código para internar:
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;
Nada realmente lujoso: FList es una TStringList que está ordenada, por lo que todo el código lo hace buscando la cadena en la lista y si ya está allí devuelve la cadena existente. Si aún no está en la lista, primero llamará a UniqueString para asegurar un recuento de referencia de 1 y luego lo agregará a la lista. (Revisé el recuento de referencia de Resultado y es 3 después de que se haya agregado ''hallo'' dos veces, como se esperaba).
Ahora al código de prueba:
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;
Esto agrega la cadena ''hallo'' al conjunto de cadenas dos veces y verifica el recuento de referencia de la cadena y también que s1 y s2 apuntan al mismo descriptor de cadena.
Cada CheckEquals funciona como se espera, pero el último. Falla con el error "esperado: <3> pero fue: <4>".
Entonces, ¿por qué la referencia es 4 aquí? Hubiera esperado 3:
- s1
- s2
- y otro en la StringList
Esto es Delphi 2007 y las cadenas son, por lo tanto, AnsiStrings.
Ah, sí, la función StringReferenceCount se implementa como:
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;
En el depurador se puede evaluar lo mismo como:
plongword(integer(pointer(s2))-8)^
Solo para agregar a la respuesta de Serg (que parece ser 100% correcta):
Si sustituyo
s2 := FPool.Intern(s2);
con
s3 := FPool.Intern(s2);
s2 := '''';
y luego verifique el recuento de referencia de s3 (y s1) es 3 como se esperaba. Es solo por haber asignado el resultado de FPool.Intern (s2) a s2 nuevamente (s2 es a la vez, un parámetro y el destino para el resultado de la función) que causa este fenómeno. Delphi introduce una variable de cadena oculta para asignar el resultado.
Además, si cambio la función a un procedimiento:
procedure TStringPool.Intern(var _s: string);
el recuento de referencia es 3 como se esperaba porque no se requiere ninguna variable oculta.
En caso de que alguien esté interesado en esta implementación de TStringPool: es de código abierto bajo el MPL y está disponible como parte de dzlib, que a su vez es parte de dzchart:
https://sourceforge.net/p/dzlib/code/HEAD/tree/dzlib/trunk/src/u_dzStringPool.pas
Pero como se dijo anteriormente: no es exactamente ciencia espacial. ;-)