string delphi delphi-2007 reference-counting string-interning

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. ;-)