c++ - memoria - recoleccion de basura en programacion
Principios de funcionamiento de los detectores de fugas de memoria (3)
¿Cómo funcionan realmente los detectores de pérdidas de memoria? ¿Cuáles son los conceptos subyacentes en general? Puede tomar C ++ como lenguaje para explicar esto.
Aquí hay un documento técnico publicado sobre cómo funciona nuestra herramienta CheckPointer.
Fundamentalmente rastrea las vidas de todos los valores (heap y stack), y sus tamaños según sus tipos según lo definido por el idioma. Esto permite que CheckPointer encuentre no solo fugas, sino también accesos vinculados fuera de la matriz, incluso para las matrices en la pila, lo que valgrind no hará.
En particular, analiza el código fuente para encontrar todos los usos del puntero. (Esta es una tarea bastante sola).
Realiza un seguimiento de los metadatos del puntero para cada puntero, que consta de
- Una referencia a los metadatos del objeto para el objeto asignado en el montón o variable global o local o función señalada por el puntero y
- El rango de direcciones del (sub) objeto del objeto al que el puntero puede acceder actualmente. Esto puede ser más pequeño que el rango de direcciones de todo el objeto; por ejemplo, si toma la dirección de un miembro de estructura, el código fuente instrumentado solo permitirá el acceso a ese miembro cuando utilice el puntero resultante.
También rastrea el tipo y la ubicación de cada objeto, es decir, si se trata de una función, una variable global, local de subproceso o local, memoria asignada en el montón o una constante literal de cadena:
- El rango de direcciones del objeto al que se puede acceder de forma segura, y
- Para cada puntero almacenado en el objeto o variable asignado al montón, una referencia a los metadatos del puntero para ese puntero.
Todo este seguimiento se logra transformando la fuente original del programa, en un programa que haga lo que hace el programa original e intercale varias rutinas de verificación o actualización de metadatos. El programa resultante se compila y se ejecuta. Cuando una verificación de metadatos falla en el tiempo de ejecución, se proporciona una traza inversa con un informe del tipo de falla (puntero no válido, puntero fuera de los límites válidos, ...)
Esto está etiquetado como C y C ++ y no se menciona ningún sistema operativo. Esta respuesta es para Windows.
C
Windows tiene el concepto de memoria virtual. Cualquier memoria que un proceso pueda obtener es memoria virtual. Esto se hace a través de VirtualAlloc () [MSDN] . Puede imaginar que el detector de fugas ponga un punto de interrupción en esa función y cada vez que se llama, obtiene la pila de llamadas y la guarda en algún lugar. Entonces puede hacer algo similar para VirtualFree()[MSDN] .
La diferencia se puede identificar y mostrar junto con las pilas de llamadas que se han guardado.
C ++
C ++ tiene un concepto diferente: toma los grandes bloques de 64 kb que obtiene de VirtualAlloc () y los divide en partes más pequeñas, llamadas Heap. El administrador del montón de C ++ proviene de Microsoft y ofrece nuevos métodos HeapAlloc () [MSDN] y HeapFree()[MSDN] .
Entonces, podría hacer lo mismo que antes, pero en realidad, esa característica ya está incorporada. La herramienta GFlags [MSDN] de Microsoft puede habilitar el seguimiento:
En este caso, ahorrará hasta 50 MB de información de pila de llamadas para llamadas de administrador de almacenamiento dinámico de C ++.
Dado que esa configuración también se puede habilitar a través del Registro de Windows, un detector de pérdida de memoria puede utilizarla fácilmente.
Concepto general
Como puede ver, el concepto general es realizar un seguimiento de las asignaciones y desasignaciones, compararlas y mostrar las pilas de llamadas de la diferencia.
Hay un par de formas diferentes en que funcionan los detectores de fugas.
Puede reemplazar la implementación de
malloc
y
free
por otras que puedan rastrear más información durante la asignación y no se preocupen por el rendimiento.
Esto es similar a cómo funciona
dmalloc
.
En general, cualquier dirección que esté
malloc
pero que no esté
free
se filtra.
La implementación básica es realmente bastante simple. Simplemente mantiene una tabla de búsqueda de cada asignación y su número de línea, y elimina la entrada cuando se libera. Luego, cuando el programa haya terminado, puede enumerar toda la memoria perdida. La parte difícil es determinar cuándo y dónde debería haberse liberado la asignación. Esto es aún más difícil cuando hay múltiples punteros a la misma dirección.
En la práctica, probablemente querrá más que solo el número de línea simple, sino más bien una traza de pila para las asignaciones perdidas.
Otro enfoque es cómo funciona valgrind que implementa una máquina virtual completa para realizar un seguimiento de las direcciones y las referencias de memoria y la contabilidad asociada. El enfoque valgrind es mucho más costoso, pero también mucho más efectivo, ya que también puede informarle sobre otros tipos de errores de memoria, como lecturas o escrituras fuera de los límites.
Valgrind esencialmente instrumenta las instrucciones subyacentes y puede rastrear cuando una dirección de memoria dada no tiene más referencias. Puede hacer esto rastreando las asignaciones de direcciones y, por lo tanto, puede decirle no solo que se perdió un trozo de memoria, sino exactamente cuándo se perdió.
C ++ hace las cosas un poco más difíciles para ambos tipos de detectores de fugas porque agrega los operadores
new
y de
delete
.
Técnicamente
new
puede ser una fuente de memoria completamente diferente a
malloc
.
Sin embargo, en la práctica, muchas implementaciones reales de C ++ solo usan
malloc
para implementar
new
o tienen una opción para usar
malloc
lugar del enfoque alternativo.
También los lenguajes de nivel superior como C ++ tienden a tener formas alternativas de nivel superior de asignar memoria como
std::vector
o
std::list
.
Un detector de fugas básico informaría las posibles asignaciones hechas por los modos de nivel superior por separado.
Eso es mucho menos útil que decir que se perdió todo el contenedor.