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 las cadenas de prácticas. Como buen codificador, también agregué pruebas unitarias y encontré algo que me desconcertó.¿Por qué esta cadena tiene un recuento de referencia de 4? (Delphi 2007)
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 de lujo: flist es un TStringList que se ordena, por lo que todo el código que hace es buscar la cadena en la lista y si ya está ahí devuelve la cadena existente. Si aún no está en la lista, primero llamará a UniqueString para garantizar un conteo de referencia de 1 y luego agregarlo a la lista. (He comprobado la cuenta de referencia del resultado y es 3 después de 'hola' se ha añadido 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 se suma la cadena 'hola' a el grupo de cadenas dos veces y verifica el recuento de referencias de la cadena y también que s1 y s2 apuntan al mismo descriptor de cadena.
Cada CheckEquals funciona como se esperaba, pero la última. Falla con el error "esperado: < 3> pero era: < 4>".
Así que, ¿por qué es el recuento de referencia 4 aquí? Lo que habría esperado 3:
- s1 s2
- y otra en la StringList
Esta es Delphi 2007 y las cuerdas son por lo tanto AnsiStrings.
Oh sí, el StringReferenceCount función 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 de la misma se puede evaluar como:
plongword(integer(pointer(s2))-8)^
sólo para añadir a la respuesta de Serg (lo que parece para ser 100% correcto):
Si reemplazo
s2 := FPool.Intern(s2);
con
s3 := FPool.Intern(s2);
s2 := '';
y después comprobar el recuento de referencia de s3 (y s1) es 3 como se esperaba. Es solo porque asigna el resultado de FPool.Intern (s2) a s2 nuevamente (s2 es ambos, 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 de la función de un procedimiento:
procedure TStringPool.Intern(var _s: string);
la cuenta de referencia es de 3 como se esperaba, ya no se requiere ninguna variable oculta.
En caso de que alguien está interesado en esta implementación TStringPool: Es de código abierto bajo la licencia 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 dijo arriba: no es exactamente ciencia espacial. ;-)
¿Podría verificar el conteo de ref para S1 al final de TestUnique también? Tengo curiosidad por saber cuál es su recuento de ref en ese punto. –
seguramente puede usar depurar dcus –
+ para no quitar tonterías a los adivinadores. –