16

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

+0

¿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. –

+0

seguramente puede usar depurar dcus –

+0

+ para no quitar tonterías a los adivinadores. –

Respuesta

14

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 se escribe s1:= Add(s1) el compilador crea una variable de cadena local de escondido, y esta variable es responsable de incrementar el recuento ref. No deberías molestarte sobre eso.

+1

¿Esto es básicamente un artefacto de los resultados de la función Delphi pasados ​​como parámetros VAR? Sabía de un efecto similar con las interfaces (que a veces se usa para guardar las construcciones try..finally) pero de alguna manera no creo que también se aplique a las cadenas. – dummzeuch

+1

@dummzeuch - Creo que es cierto. El compilador simplemente no puede pasar s1 como 'var' en' s1: = Add (s1) ', por lo que crea una variable oculta, la pasa como' var' y la asigna a s1 (incrementando el recuento de ref) – kludg

Cuestiones relacionadas