2008-09-15 13 views
16

¿Alguien conoce una "técnica" para descubrir fugas de memoria causadas por punteros inteligentes? Actualmente estoy trabajando en un gran proyecto escrito en C++ que utiliza en gran medida punteros inteligentes con recuento de referencias. Obviamente, tenemos algunas pérdidas de memoria causadas por punteros inteligentes, que todavía se hace referencia en algún lugar del código, por lo que su memoria no se libera. Es muy difícil encontrar la línea de código con la referencia "innecesaria", que hace que el objeto correspondiente no se libere (aunque ya no sea útil).Buscar fugas de memoria causadas por punteros inteligentes

Encontré algunos consejos en la web, que propuso recopilar las pilas de llamadas de las operaciones de incremento/decremento del contador de referencia. Esto me da una buena pista, qué pieza de código ha causado que el contador de referencia se incremente o disminuya.

Pero lo que necesito es algún tipo de algoritmo que agrupe los correspondientes "aumentos/disminuciones de llamadas" juntos. Después de eliminar estos pares de llamadas, espero tener (al menos) un "aumento de la pila de llamadas", que me muestra el fragmento de código con la referencia "innecesaria", que provocó que el objeto correspondiente no se liberara. ¡Ahora no será un gran problema arreglar la fuga!

¿Pero alguien tiene una idea para un "algoritmo" que hace la agrupación?

El desarrollo se lleva a cabo en Windows XP.

(espero que alguien entendido, lo que he tratado de explicar ...)

EDIT: Estoy hablando de fugas causadas por las referencias circulares.

+0

No estoy seguro Entiendo ... ¿puede hacer referencia a un lenguaje de programación o una plataforma más específicamente? Las fugas de memoria pueden variar en su manejo. – zxcv

+0

Punteros inteligentes generalmente significan C++/STL. –

+0

Hay algo de confusión aquí. Creo que valdría la pena distinguir entre la memoria asignada que no tiene un puntero (no debería suceder con los punteros inteligentes), las fugas causadas por referencias circulares y los objetos cuyo tiempo de vida se extiende innecesariamente porque el puntero se mantiene. –

Respuesta

15

Tenga en cuenta que una de las fuentes de fugas con punteros inteligentes de conteo de referencias son punteros con dependencias circulares. Por ejemplo, A tiene un puntero inteligente a B, y B tiene un puntero inteligente a A. Ni A ni B serán destruidos.Tendrás que encontrar y luego romper las dependencias.

Si es posible, use boost punteros inteligentes y use shared_ptr para los punteros que se supone que son propietarios de los datos, y weak_ptr para los punteros que no se supone que invocan delete.

1

Si tuviera que tomaría el registro y escribo un guión rápido de hacer algo como lo siguiente (el mío es en Ruby):

def allocation?(line) 
    # determine if this line is a log line indicating allocation/deallocation 
end 

def unique_stack(line) 
    # return a string that is equal for pairs of allocation/deallocation 
end 

allocations = [] 
file = File.new "the-log.log" 
file.each_line { |line| 
    # custom function to determine if line is an alloc/dealloc 
    if allocation? line 
    # custom function to get unique stack trace where the return value 
    # is the same for a alloc and dealloc 
    allocations[allocations.length] = unique_stack line 
    end 
} 

allocations.sort! 

# go through and remove pairs of allocations that equal, 
# ideally 1 will be remaining.... 
index = 0 

while index < allocations.size - 1 
    if allocations[index] == allocations[index + 1] 
    allocations.delete_at index 
    else 
    index = index + 1 
    end 
end 

allocations.each { |line| 
    puts line 
} 

Esto básicamente pasa por el registro y captura cada asignación/desasignación y almacena un valor único para cada par, luego clasifíquelo y elimine los pares que coinciden, vea lo que queda.

actualización

: Lo siento por todas las ediciones intermedias (I accidentalmente publicado antes de que se hizo)

+0

Nota, asumí que ya tiene un registro con los alloc/traces libres de los que habla en su pregunta, pero las otras respuestas pueden ayudarlo a resolver las pérdidas de memoria en general –

6

La manera de hacerlo es simplemente: - en cada AddRef() registro de llamada-pila, - juego de lanzamiento () lo elimina. De esta forma, al final del programa, me quedan AddRefs() sin versiones de mecanizado. No hay necesidad para que coincida con los pares,

2

Lo que he hecho de resolver esto es para anular el malloc/nueva & libre/borrar operadores de tal manera que no perder en una estructura de datos tanto como sea posible acerca de la operación que están realizando.

Por ejemplo, cuando anulando malloc/nueva, puede crear un registro de la dirección de la persona que llama, la cantidad de bytes solicitados, el valor del puntero asignado regresó y un identificador de secuencia para todos sus registros pueden ser secuenciados (hago no sé si manejas hilos, pero también debes tenerlo en cuenta).

Al escribir las rutinas libre/eliminar, también hago un seguimiento de la dirección de la persona que llama y la información del puntero.Luego miro hacia atrás en la lista e intento hacer coincidir la contraparte malloc/new usando el puntero como mi clave. Si no lo encuentro, levante una bandera roja.

Si puede pagarlo, puede insertar en sus datos la ID de la secuencia para estar absolutamente seguro de quién y cuándo se realizó la asignación. La clave aquí es identificar de forma única cada par de transacciones tanto como podamos.

Luego tendrá una tercera rutina que muestra su asignación de memoria/historial de desasignación, junto con las funciones que invocan cada transacción. (Esto se puede lograr al analizar el mapa simbólico de su enlazador). Sabrá cuánta memoria tendrá asignada en cualquier momento y quién lo hizo.

Si no tiene suficientes recursos para realizar estas transacciones (mi caso típico para los microcontroladores de 8 bits), puede generar la misma información a través de un enlace serie o TCP a otra máquina con recursos suficientes.

2

Dado que usted ha dicho que usted está utilizando Windows, puede ser capaz de tomar ventaja de modo de usuario utilidad montón de volcado de Microsoft, UMDH, que viene con el Debugging Tools for Windows. UMDH realiza instantáneas del uso de memoria de la aplicación, registra la pila utilizada para cada asignación y le permite comparar varias instantáneas para ver qué llamadas a la memoria "filtrada" del asignador. También traduce los rastreos de pila a símbolos para usted que usan dbghelp.dll.

También hay otra herramienta de Microsoft llamada "LeakDiag" que admite más asignadores de memoria que UMDH, pero es un poco más difícil de encontrar y no parece mantenerse activamente. La última versión tiene al menos cinco años, si no recuerdo mal.

2

No se trata de encontrar una fuga. En el caso de los smart-pointers lo más probable es que dirija a algún lugar genérico como CreateObject(), que se llama miles de veces. Se trata de determinar qué lugar del código no se llamaba Release() en el objeto ref-counted.

1

Soy un gran fan de Google's Heapchecker - no captará todas las fugas, pero se lleva la mayoría de ellas. (Sugerencia: Vincúlela a todos sus unittests.)

4

Si puede reproducir la fuga de una manera determinista, una técnica simple que utilizo a menudo es enumerar todos los punteros inteligentes en su orden de construcción (utilice un estático contador en el constructor) e informe esta ID junto con la fuga. A continuación, ejecute el programa nuevamente y active un DebugBreak() cuando se construya el puntero inteligente con el mismo ID.

también se debe considerar esta gran herramienta: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

+0

Puede hacer esto junto con el volcado de pila. Esto le permitiría emparejar las salidas a través del UID de los refs/derefs – Ray

4

Lo que hago es envolver el puntero inteligente con una clase que lleva FUNCIÓN y LÍNEA parámetros. Incremente un recuento para esa función y línea cada vez que se llame al constructor, y disminuya el recuento cada vez que se llame al destructor. luego, escriba una función que vuelque la información de función/línea/conteo. Eso te dice donde todas sus referencias fueron creados

+0

He utilizado esta técnica. Si todo sale de CreateObj, pase a la persona que llama de la FUNCIÓN/LÍNEA de CreateObj. (Por supuesto, hacer esto a través de macros. Incluso mejor si tiene una función "¿Quién me llamó?") –

+1

@ Krazy/Joe: ¿Podría indicarme algún ejemplo que muestre cómo encontrar "¿Quién me llamó?" – anand

+1

La identificación de la persona que llama es solo la primera parte de una pila, por lo que http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes le ofrece necesitas. Backtrace()/execinfo.h. –

4

Para detectar ciclos de referencia, debe tener un gráfico de todos los objetos contados por referencia. Tal gráfico no es fácil de construir, pero se puede hacer.

Cree un set<CRefCounted*> global para registrar objetos vivientes de recuento de referencias. Esto es más fácil si tiene una implementación AddRef() común: simplemente agregue el puntero this al conjunto cuando el recuento de referencias del objeto va de 0 a 1. De manera similar, en Release() elimina el objeto del conjunto cuando el recuento de referencias va de 1 a 0.

A continuación, proporcione alguna forma de obtener el conjunto de objetos referenciados de cada CRefCounted*. Podría ser un virtual set<CRefCounted*> CRefCounted::get_children() o lo que más le convenga. Ahora tienes una forma de recorrer el gráfico.

Finalmente, implemente su algoritmo favorito para cycle detection in a directed graph. Comience el programa, cree algunos ciclos y ejecute el detector de ciclo. ¡Disfrutar! :)

+0

Me gusta su forma de pensar, pero no creo que haya una manera fácil de obtener objetos referenciados de un objeto: get_children –

+0

@lzprgmr, Sí, 'get_children()' tiene que codificarse para cada clase participante manualmente con el conocimiento de las referencias que mantiene la clase. A eso me refiero con "no es fácil de construir". – Constantin

0

El primer paso podría ser saber qué clase está goteando. Una vez que lo sepa, puede encontrar quién está aumentando la referencia: 1. ponga un punto de interrupción en el constructor de clase que está envuelto por shared_ptr. 2. ingrese con el depurador dentro de shared_ptr cuando aumente el recuento de referencias: mire la variable pn-> pi _-> use_count_ Tome la dirección de esa variable evaluando la expresión (algo como esto: & this-> pn-> pi_. use_count_), obtendrá una dirección 3. En el depurador visual studio, vaya a Depurar-> Nuevo punto de interrupción-> Nuevo punto de interrupción de datos ... Ingrese la dirección de la variable 4. Ejecute el programa. Su programa se detendrá cada vez que algún punto en el código aumenta y disminuye el contador de referencia. Luego debe verificar si coinciden.

Cuestiones relacionadas