2012-05-26 15 views
11

Según FastMM4, el programa Delphi en el que estoy trabajando en este momento está filtrando muchas cosas. AnsiStrings para ser precisos:Aplicación de Delphi con fuga de AnsiStrings

enter image description here

La aplicación (http://sourceforge.net/projects/orwelldevcpp/) usado para filtrar mucho más de otros tipos de datos, pero podría reportar FastMM4 donde se creó la instancia, por lo que logró arreglar eso. Lo extraño es que FastMM4 no informa la ubicación de estas fugas en absoluto.

Editar: parece que sí, después de todo, ver las respuestas para la solución. De todos modos, la pregunta sigue en pie: ¿cómo demonios estoy filtrando estas cosas?

Entonces, ehm, desafortunadamente, no tengo idea de qué buscar. Quiero decir, si estas cosas salen de su alcance, deberían liberarse automáticamente de manera correcta (a pesar de que están en el montón).

Logré rastrear algunas filtraciones por comentarios aleatorios y ver qué pasaría con los conteos. He aquí un ejemplo:

// simply passing it a constant creates a leak... 
MainForm.UpdateSplash('Creating extra dialogs...'); 

procedure TMainForm.UpdateSplash(const text : AnsiString); 
begin 
    if not devData.NoSplashScreen then // even if this branch is NOT taken 
    SplashForm.Statusbar.SimpleText := 'blablabla' + text; 
end; 

// And even if the function call itself is placed within a NOT taken branch! 

Aquí hay otro ejemplo de una fuga:

// Passing this constants produces leaks... 
procedure TCodeInsList.AddItemByValues(const a, b, c: AnsiString;...); 
var 
    assembleditem : PCodeIns; 
begin 
    new(assembleditem); 
    assembleditem^.Caption:=a; 
    assembleditem^.Line:=b; 
    assembleditem^.Desc:=c; 
    ... 
    fList.Add(assembleditem); 
end; 

// ... even when calling this on WM_DESTROY! 
destructor TCodeInsList.Destroy; 
var 
    I: integer; 
begin 
    for I := 0 to fList.Count - 1 do 
    Dispose(fList[I]); 
    fList.Free; 
    inherited Destroy; 
end; 

// produces leaks!? 

hay un buen montón de preguntas de fugas cadena aquí, pero ninguno realmente aclarar qué patrones uno debe buscar. Google tampoco proporciona ninguno.

Edición: entonces, tengo que buscar las constantes pasadas. ¿Pero por qué?

¿Alguna idea, ehm?

+0

No puedo cargar el proyecto de sourceforge en este momento. ¿Hay alguna posibilidad de que la forma principal no se destruya adecuadamente y, por lo tanto, deje cadenas colgando? ¿Eso lo haría? –

+0

versión delphi?Si puede, pruebe con aqtime le dirá exactamente dónde está la fuga. –

+0

@RichardA: Como puede ver en source \ devcpp.dpr, la splashform se libera usando 'Free'. Intentará agregar caFree al evento OnClose. @ Warren: Dudo mucho que aqtime pueda contarme más que FastMM4, gpProfiler y MemCheck. Y también necesito actualizar a XE para aqtime (usando D7 ahora). Mi universidad tiene una licencia D2009 flotando en algún lugar (no para gente de EE como yo), pero aqtime ni siquiera es compatible con eso. – Orwell

Respuesta

14

No necesita asignar cadenas explícitamente. Además de modificar los recuentos de referencia, los campos de cadenas de objetos o registros también pueden tener fugas. Por ejemplo,

type 
    PRecord = ^TRecord; 
    TRecord = record 
    S: string; 
    end; 

procedure TForm1.Button4Click(Sender: TObject); 
var 
    r: PRecord; 
begin 
    GetMem(r, SizeOf(r^)); 
    Initialize(r^); 
    r.S := ' '; 
    FreeMem(r); 

En el ejemplo anterior, ya que la memoria del disco en sí se libera, se FastMM informar sólo la cadena de filtrado.


En cualquier caso, FastMM que no muestra un seguimiento de la pila en el cuadro de diálogo no significa que carece de esa información. Asegúrese de tener FullDebugMode, LogMemoryLeakDetailToFile y LogErrorsToFile definidos en 'FastMM4Options.inc'. Luego busque un archivo '[ExecutableName] _MemoryManager_EventLog.txt' en el directorio del ejecutable.

Para el ejemplo anterior, FastMM produce el siguiente archivo:

 
--------------------------------2012/5/27 4:34:46-------------------------------- 
A memory block has been leaked. The size is: 12 

Stack trace of when this block was allocated (return addresses): 
40305E 
404B5D 
404AF0 
45C47B 
43D726 
42B0C3 
42B1C1 
43D21E 
76C4702C [GetWindowLongW] 
77AE3CC3 [Unknown function at RtlImageNtHeader] 

The block is currently used for an object of class: Unknown 

The allocation number is: 484 

Current memory dump of 256 bytes starting at pointer address 7EF8DEF8: 
01 00 00 ... 
... 

Ahora se puede ejecutar la aplicación, pausa y luego buscar las direcciones. Para el registro de arriba y la aplicación de prueba, las direcciones, resuelve:

 
Stack trace of when this block was allocated (return addresses): 
40305E -> _GetMem 
404B5D -> _NewAnsiString 
404AF0 -> _LStrAsg 
45C47B -> TForm1.Button4Click (on FreeMem line) 
43D726 -> TControl.Click 
... 


edición: En lugar de buscar manualmente las direcciones, generar un archivo de mapa detallado a través de opciones de enlazador y FastMM lo hará (gracias a El comentario de Mason).


Su edición en la pregunta refleja una fuga bastante similar a la del ejemplo anterior. Si el 'fList' es un TList normal, solo contiene punteros y no tiene conocimiento de a qué apuntan esos punteros. Por lo tanto, cuando desecha el puntero, solo se libera la memoria asignada para el puntero, no los campos del registro. Por lo que las fugas no tienen nada que hacer constantes pasados ​​a las funciones, pero es como el siguiente patrón:

Para el registro que hay que eliminar, el código debe encasillarse el puntero a su tipo:

Dispose(PCodeIns(p)); 

por lo su 'TCodeInsList.Destroy' debe ser:

destructor TCodeInsList.Destroy; 
var 
    I: integer; 
begin 
    for I := 0 to fList.Count - 1 do 
    Dispose(PCodeIns(fList[I])); 
    fList.Free; 
    inherited Destroy; 
end; 


al final, el patrón que está buscando parece estar buscando lugares donde el código de intenciones a reco libre rds (objetos menos probables) que tienen campos de cadena. Buscando Dispose, un poco menos probable FreeMem, incluso menos probable FreeInstance para liberar memoria de objetos/registros que FastMM muestra como se filtró la memoria asignada podría ayudar.

+0

Hmm, debo haberlo ignorado cuando buscaba otras filtraciones, pero sí, tiene razón, FastMM4 muestra alguna información: 102DF8 [SynEditKeyCmds] [SynEditKeyCmds] [@ GetMem]. Multiplique eso por 40000, dentro de un archivo de texto de 150MiB para obtener la idea. Gracias, lo investigaré. – Orwell

+1

BTW ese trabajo se vuelve mucho más fácil si le dices al vinculador que genere un archivo de mapa detallado. Entonces FastMM puede hacer las búsquedas por usted. –

+0

@Mason - Gracias, me preguntaba qué me faltaba en la aplicación de prueba. :) –

4

Tiene razón en que las cuerdas deben limpiarse automáticamente. Sin embargo, he visto algunas formas de arruinarlo.

La primera es si está haciendo cosas con la estructura de datos de cadena directamente que puede romper el recuento de referencias. Esto es lo más probable, con la cantidad de cadenas que está filtrando.

El otro llama a Halt y deja referencias de cadena en la pila. Pero no vas a dejar 40,000 referencias de cadenas en la pila, así que buscaré un código que pase una cadena y luego toque su recuento de referencias.

+0

Estoy cien por ciento seguro de que no estoy usando Detener en ninguna parte. Estoy usando Exit con bastante frecuencia, pero eso no debería importar mucho. Y uhm, no, no estoy jugando con el índice cero, por ejemplo. – Orwell

+4

@Orwell 'exit' es seguro para usar en cualquier lugar: irá al bloque oculto' try..finally' generado para cualquier cadena local, y manejará el recuento de referencias como se espera. –

1

Con palabras cortas, los tipos de cadenas incorporadas en Delphi se cuentan por referencia. Los métodos de asignación y eliminación de memoria no se ocupan de actualizar el recuento de referencias, de modo que el compilador no sabe que las cadenas en sus registros se pueden liberar en realidad.

Se desaconseja definir un registro con tipos de cadenas contadas de referencia. Tuve la misma confusión antes. Si echas un vistazo a la fuente de la biblioteca Delphi. Encontrará muchos registros que no tienen PChar.

Some discuss about records

1

La forma más común de fugas cuerdas es tener un registro que contiene una cadena y un puntero a ese registro. Si simplemente hace un Dispose() de ese puntero, el compilador simplemente liberará el puntero, no todas las cosas en el registro subyacente. Siempre asegúrese de que su código de eliminación le informe al compilador de lo que está desechando.

Por ejemplo, supongamos que en un TTreeView pongo PMyRecord =^MyRecord en el Node.Data. Si, al final, recorre todos los nodos y simplemente tira Dispose (Node.Data), las cadenas de MyRecord no se manejarán correctamente. En este caso, necesitaría desechar (PMyRecord (Node.Data)).

Cuestiones relacionadas