2009-01-13 5 views
27

¿Cómo puedo rastrear las asignaciones de memoria en C++, especialmente las realizadas por new/delete. Para un objeto, puedo anular fácilmente el operator new, pero no estoy seguro de cómo anular globalmente todas las asignaciones para que pasen por mi personalizado new/delete. Esto no debería ser un gran problema, pero no estoy seguro de cómo se supone que debe hacerse (#define new MY_NEW?).Cómo rastrear las asignaciones de memoria en C++ (especialmente nuevas/eliminar)

Tan pronto como esto funcione, supongo que es suficiente tener un mapa en alguna parte del puntero/ubicación de la asignación, así puedo hacer un seguimiento de todas las asignaciones que están actualmente 'activas' y - al final de la aplicación - verificar las asignaciones que no se han liberado.

Bueno, esto parece una vez más algo que seguramente se ha hecho varias veces al menos, por lo que cualquier buena biblioteca por ahí (preferiblemente una portátil)?

+0

No hay una respuesta genérica disponible. Proporcione más información sobre el sistema operativo y la plataforma utilizada. – kauppi

+0

Necesitaría una solución que funcione al menos en Linux y Windows, y preferiblemente también en Mac OS. – Anteru

Respuesta

26

Yo le recomendaría que use valgrind para Linux. Capturará memoria no liberada, entre otros errores, como escribir en la memoria no asignada. Otra opción es mudflap, que también le informa sobre la memoria no liberada. Use las opciones -fmudflap -lmudflap con gcc, luego inicie su programa con MUDFLAP_OPTIONS=-print-leaks ./my_program.

Aquí hay un código muy simple. No es adecuado para el seguimiento sofisticado, pero pretende mostrarle cómo lo haría en principio, si tuviera que implementarlo usted mismo. Algo como esto (cosas omitidas que llaman al new_handler registrado y otros detalles).

template<typename T> 
struct track_alloc : std::allocator<T> { 
    typedef typename std::allocator<T>::pointer pointer; 
    typedef typename std::allocator<T>::size_type size_type; 

    template<typename U> 
    struct rebind { 
     typedef track_alloc<U> other; 
    }; 

    track_alloc() {} 

    template<typename U> 
    track_alloc(track_alloc<U> const& u) 
     :std::allocator<T>(u) {} 

    pointer allocate(size_type size, 
        std::allocator<void>::const_pointer = 0) { 
     void * p = std::malloc(size * sizeof(T)); 
     if(p == 0) { 
      throw std::bad_alloc(); 
     } 
     return static_cast<pointer>(p); 
    } 

    void deallocate(pointer p, size_type) { 
     std::free(p); 
    } 
}; 

typedef std::map< void*, std::size_t, std::less<void*>, 
        track_alloc< std::pair<void* const, std::size_t> > > track_type; 

struct track_printer { 
    track_type * track; 
    track_printer(track_type * track):track(track) {} 
    ~track_printer() { 
     track_type::const_iterator it = track->begin(); 
     while(it != track->end()) { 
      std::cerr << "TRACK: leaked at " << it->first << ", " 
         << it->second << " bytes\n"; 
      ++it; 
     } 
    } 
}; 

track_type * get_map() { 
    // don't use normal new to avoid infinite recursion. 
    static track_type * track = new (std::malloc(sizeof *track)) 
     track_type; 
    static track_printer printer(track); 
    return track; 
} 

void * operator new(std::size_t size) throw(std::bad_alloc) { 
    // we are required to return non-null 
    void * mem = std::malloc(size == 0 ? 1 : size); 
    if(mem == 0) { 
     throw std::bad_alloc(); 
    } 
    (*get_map())[mem] = size; 
    return mem; 
} 

void operator delete(void * mem) throw() { 
    if(get_map()->erase(mem) == 0) { 
     // this indicates a serious bug 
     std::cerr << "bug: memory at " 
        << mem << " wasn't allocated by us\n"; 
    } 
    std::free(mem); 
} 

int main() { 
    std::string *s = new std::string; 
     // will print something like: TRACK: leaked at 0x9564008, 4 bytes 
} 

tenemos que usar nuestro propio asignador de nuestro mapa, debido a la estándar utilizará nuestro operador reemplazado nueva, lo que resultaría en un bucle infinito.

Asegúrese de que si anula el operador nuevo, use el mapa para registrar sus asignaciones. Eliminar la memoria asignada por las formas de ubicación de nuevo usará ese operador de eliminación también, por lo que puede ser complicado si algún código que no conoce tiene operador sobrecargado nuevo que no usa su mapa, porque la eliminación del operador le dirá que no fue asignado y use std::free para liberar la memoria.

También tenga en cuenta, como Pax señaló también para su solución, esto solo mostrará las fugas causadas por el código utilizando nuestro propio operador definido new/delete. Por lo tanto, si desea usarlos, coloque su declaración en un encabezado e inclúyala en todos los archivos que deben ser vistos.

+0

Gran publicación. Me ayudó mucho tu ejemplo para rastrear y corregir una fuga de memoria en un dispositivo incrustado :) – tkarls

+0

¡Buen ejemplo! Una cosa a tener en cuenta, este código no es seguro para subprocesos, por lo que en un entorno de subprocesos múltiples (donde 'new' y' delete' serían llamados desde múltiples hilos) tendría que proteger el acceso al mapa 'track' con un' std :: mutex'. – gbmhunter

1

No responder directamente a su pregunta, pero si realmente sólo quiere obtener una lista de heap-objetos filtrados al final del programa, es posible que también acaba de ejecutar el programa con valgrind.

Para MS VS puede jugar con the Debug CRT Heap. No tan simple como valgrind, un poco demasiado para explicar aquí, pero puede hacer lo que quieras.

+0

Sí, estoy usando estos en este momento, pero me gustaría cambiar el asignador de memoria (especialmente para rastrear la memoria en varias categorías), así que necesito una solución personalizada aquí. – Anteru

7

Bueno, puede volver a implementar los operadores globales nuevos y eliminar para darle la funcionalidad que desea, pero desaconsejaría que a menos que esta sea la única forma de rastrear las asignaciones de memoria, debido a las restricciones de su plataforma para ejemplo.

Los depuradores de memoria están disponibles para la mayoría de las plataformas de desarrollo comunes. Eche un vistazo a PurifyPlus para una solución comercial que funciona en Windows y varios Unixes o valgrind para uno de código abierto que funciona en Linux (y potencialmente otros sistemas operativos, pero solo lo he usado en Linux).

Si tiene la intención de reemplazar los operadores globales, eche un vistazo a this article.

7

Puede usar el código en http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml con las siguientes modificaciones: el código proporcionado solo funciona si tiene un gran archivo fuente honkin '. Resolví esto para otra pregunta en SO (here).

Para empezar, no cambie stdafx.h, haga sus modificaciones en sus propios archivos.

Hacer un mymemory.h archivo de cabecera por separado y poner los prototipos de las funciones en el mismo, por ejemplo (tenga en cuenta que esto no tiene ningún cuerpo ):

inline void * __cdecl operator new(unsigned int size, 
    const char *file, int line); 

También en esa cabecera, poner los otros prototipos de AddTrack(), DumpUnfreed(), etc., y el #defines, typedef y la declaración extern:

extern AllocList *allocList; 

Luego, en un nuevo mymemory.cpp (que también incluyen # de mymemory.h), ponen el real definición de allocList junto con toda la rea l funciona (no solo los prototipos) y agrega ese archivo a su proyecto.

Luego, #include "mymemory.h" en cada archivo fuente en el que necesite rastrear la memoria (probablemente todos ellos).Debido a que no hay definiciones en el archivo de encabezado, no obtendrá duplicados durante el enlace y debido a que las declaraciones están allí, tampoco obtendrá referencias indefinidas.

Tenga en cuenta que esto no hará un seguimiento de las pérdidas de memoria en el código que no compila (por ejemplo, bibliotecas de terceros), pero debe informarle sobre sus propios problemas.

3

Para nuestra plataforma Windows C++, utilizo VLD, Visual Leak Detector, que es casi demasiado fácil de implementar y rastrea e informa sobre fugas de memoria cuando se cierra la aplicación, lo mejor de todo es gratis y la fuente está disponible. El sistema se puede configurar para informar de varias maneras (registrador de disco, IDE, XML, etc.) y ha sido invaluable para detectar fugas en los Servicios de Windows que siempre son un desafío para la depuración. Por lo tanto, mientras busca una solución portátil, si desea hacerla suya, puede ver la fuente de orientación. Espero eso ayude.

Para citar el sitio:

Es una forma muy efectiva de forma rápida diagnóstico, y fijar, pérdidas de memoria en aplicaciones C/C++.

http://dmoulding.googlepages.com/vld

-1

Si se refiere a ello como un ejercicio de programación, puede ser que le dará mucho más conocimiento para escribir su propia clase puntero inteligente (s) en su lugar, y constantemente usarlos lo largo de este un proyecto (o módulo de un proyecto).

3

En Linux, hay al menos dos métodos tradicionales:

  • malloc() y free() (y otras funciones relacionadas con la memoria) son símbolos débiles, lo que significa que sólo tiene que reimplementar ellos y sus versiones se utilizará. Para un ejemplo de implementación: ver cerca eléctrica.
  • Con la variable de entorno LD_PRELOAD, puede anular símbolos (tanto débiles como fuertes) en bibliotecas compartidas con los símbolos que se encuentran en las bibliotecas contenidas en la variable de entorno LD_PRELOAD. Si compila una biblioteca compartida con malloc(), free() y amigos, ya está todo listo. De nuevo, la cerca eléctrica demuestra esto.

Como tal, no solo captura nuevas y elimina, sino también las funciones de asignación de memoria de estilo C. Todavía no he hecho esto en Windows, pero he visto métodos para reescribir también cómo se vinculan los archivos DLL (aunque recuerdo que fueron un poco torpes).

Sin embargo, tenga en cuenta que, aparte de que estas son técnicas interesantes, recomendaría utilizar valgrind para hacer lo que desee por encima de cualquier otra cosa.

0

Si está desarrollando bajo Linux, una de las mejores herramientas para esto (por ejemplo, detectar fugas de memoria, rastrear asignaciones hechas en ciertos lugares de código) es valgrind, particularmente su herramienta de macizo. La única desventaja es que el programa se ejecuta más lento (o mucho más lento) por lo que solo es útil para la depuración.

0

Noté que muchas de las otras respuestas se centran en las herramientas que puede usar. He usado algunos de ellos y me ayudan mucho.

Pero como ejercicio de programación, y viendo que trabajas con C++, deberás anular el nuevo y eliminar global, así como malloc, libre y realloc. Uno pensaría que solo reemplazar las nuevas y eliminar sería suficiente, pero std :: string y otras clases probablemente usarán malloc y especialmente realloc.

Luego, una vez que tenga esto en su lugar, puede comenzar a agregar encabezados para verificar sobrescrituras de memoria, registrar trazas de pila por asignación, etc.

En general, le recomiendo que vaya con una de las herramientas mencionadas aquí, pero podría ser divertido escribir su propio sistema.

+0

Tengo serias dudas de que una cadena std:; use realloc, ya que tiene que usar el asignador provisto, que no es compatible con realloc. – Anteru

23

Para ser específico, use la herramienta macizo de valgrind. A diferencia de memcheck, macizo no se preocupa por el uso ilegal de la memoria, sino que hace un seguimiento de las asignaciones a lo largo del tiempo. Hace un buen trabajo al medir de manera eficiente el uso de la memoria de montón de un programa. La mejor parte es que no tienes que escribir ningún código. Proveedores:

http://valgrind.org/docs/manual/ms-manual.html

O si usted es muy impaciente:

valgrind --tool=massif <executable> <args> 
ms_print massif.out.<pid> | less 

esto le dará un gráfico de las asignaciones a través del tiempo, y se remonta a los grandes ocurridos en las asignaciones. Esta herramienta se ejecuta mejor en Linux, no sé si hay una variente de Windows. Es hace trabajo en OS X.

¡Buena suerte!

+0

¡Una buena, otra herramienta valgrind que no conocía! – BenC

0

No es barato, pero solía encontrar en mis días de C++ que purify era la mejor herramienta para depurar fugas y otros problemas de memoria (lo mismo ahora es propiedad de IBM, por lo que surport se fue cuesta abajo). Bounds Checker fue del agrado de algunas personas, pero no funcionó bien para el software que estaba desarrollando.

0

Puede utilizar añadir un archivo de cabecera (MemTracker.h) dada en este link a su solución para realizar un seguimiento de la asignación de memoria/cancelación de asignación en C y C++. Muestra si tiene una pérdida de memoria y qué línea de código es la responsable.

0

Si necesito una herramienta, generalmente empiezo por lo que proporciona mi compilador/biblioteca estándar.

  • Si usa glibc puede usar mtrace. Instala un enlace global que registra cada función de asignación de memoria glibc (malloc, realloc, memalign, libre y todo implementado sobre ellos como nuevo/eliminar)
  • Si usa Microsoft CRT, puede mirar CRT Debug Heap Details. Hay ejemplos de cómo instalar la versión de depuración de las funciones de asignación de memoria, obtener estadísticas del montón, encontrar fugas de memoria, etc.
0

Comprobar este pequeño código a mano, ahora en lugar de new uso NEW y realizar un seguimiento de todas las asignaciones en el NewHelper constructor:

#include <iostream> 

class NewHelper 
{ 
    private : 
    void* addr = nullptr; 
     public : 
     NewHelper(void * addr_) 
     { 
      addr = addr_; 
      std::cout<<addr<<std::endl; 
     } 
     template <class T> 
     operator T() 
     { 
      return (T)addr; 
     } 
}; 
#define NEW (NewHelper)(void*)new 
int main() 
{ 
    int * i = NEW int(0); 
return 0; 
} 
+0

Esto no hará un seguimiento de las asignaciones de ningún código de biblioteca. Además, su '(void *)' sacrifica el tipo de seguridad que obtenemos con 'new'. –

+0

el compilador reconvertirá el tipo usando NewHelper :: operator T, de todos modos, he codificado un trazador de memoria fullc/C++ que rastrea cada asignación en sus archivos y los archivos stdlib ...., puedo venderlo si alguien está interesado, características : - Log stacktrace para todas las asignaciones que nunca se liberan: - Log stacktrace para todas las asignaciones libres más de una vez. - Stacktrace para asignaciones invalide free() ... - mostrando stacktrace para todas las asignaciones realizadas en atributos contructor's cuando se asigna el objeto padre pero nunca eliminado (constructor no llamado) –

Cuestiones relacionadas