¡Esta es una revisión vieja del documento!
El memory leak consiste en la acomulación de memoria que ya no se utiliza y que no ha sido liberada, esto causa que el rendimiento de los programas se vaya deteriorando. Los memory leaks surgen sobretodo en métodos en los que se reserva memoria y al salir de este scope esta no se ha liberado (haciendola inalcanzable para liberarla posteriormente), o cuando en bucles también se llama hace un new dentro de la iteración pero no un delete.
Ejemplos:
/* Ejemplo 1 *****************************/ void foo() { int *i = new int; *i = 44; } /* Ejemplo 2 *****************************/ void foo () { char* str = new char[20]; strcpy(str,"memory leak"); } /* Ejemplo 3 *****************************/ Sample* a = new Sample; Sample* b = new Sample; a = b; delete a; // Elimina b delete b; // Error! Ya eliminado. /* Ejemplo 4 *****************************/ double* d = new double[10]; delete d; // esto es como hacer 'delete d[0];' hay que utilizar 'delete [] d;' /* Ejemplo 5 *****************************/ int *i; int r = 0; while(r < 10) { i = new int; r ++; } delete i; // sólo se elimina el último creado, quedan 9 más por eliminar
Existen una serie de funciones (sólo en la programación para windows) son para el control de memoria desde windows y únicamente están activas cuando se llaman en una compilación en modo “debug”, la salida se hará en la pantalla de resultados.
Recuerda que para utilizarlas has incluir “crtdbg.h”.
#define _CRTDBG_MAP_ALLOC antes de los #include, esta función mostrará el fichero y la línea donde se produce el leak.
En rtdbg.h está definida la estructura _CrtMemState que almacena información sobre el heap de memoria, infromación como el tipo de bloque ('_NORMAL_BLOCK', '_CLIENT_BLOCK' o 'FREE_BLOCK'), el número de bytes reservados… Puede ser utilizada en otras funciones como:
_CrtMemState s1, s2, s3; _CrtMemCheckpoint( &s1 ); // código _CrtMemCheckpoint( &s2 ); if ( _CrtMemDifference( &s3, &s1, &s2) ) _CrtMemDumpStatistics( &s3 );
Podemos hacer un pequeño mecanismo para muestrar las estadísticas de los memory leaks fácilmente. Sería crear una clase con un _CrtMemState en el que se haría un checkpoint de la memoria sobre él en el constructor. En el destructor crearíamos 2 más, el primero donde se almacenaría la diferencia y el segundo un checkpoint de la situación actual, se comprovaría su diferencia y si esta existe se mostrarían las estadísticas. Para utilizarlo sólo deberíamos de crear un objeto al principio de la función que queremos mirar y olvidarnos de él, automáticamente se llamará al destructor cuando salga de esta.
int main(int argc, char* argv[]) { FindMemoryLeaks fml; int * p = new int; return 0; }
O Resource Acquisition Is Initialization.
Es un patrón para C++ que sirve para garantizar la correcta inicialización y liberación de un recurso (lock de un mutex, un fichero, un socket o una porción de memoria), consiste en envolverlo en una clase que se encargará de inicializarlo, cuando un objeto de esta se cree, y liberarlo, cuando el objeto se destruya.
Es muy importante entender cómo organizar las dos acciones de un recurso (inicialización y destrucción), entonces, al usar RAII envolvemos el recurso en una clase, esta lo inicializa en el constructor y lo destruye en el destructor. Luego, aprovechando el scope de una variable\objeto, la instanciaremos en una función que cuando acabe eliminará automáticamente la instancia llamando así al destructor, pudiendose así el programador olvidarse de la liberación.
Por ejemplo un código que utiliza un fichero y no utiliza RAII:
FILE* f(fopen("name", "r")); ... if (something) { fclose(f); return; } ... fclose(f); return;
Este código es un uso de un fichero normal y corriente, del cual nos hemos de preocupar de abrir y cerrar (y puede que varias veces según la excepción). Cuando empleamos RAII el fichero queda en una clase, cuando esta se destruya (al final de una función) se cerrará también el archivo:
class file { public: file( const char* filename ) : m_file_handle(std::fopen(filename, "w+")) { if( !m_file_handle ) throw std::runtime_error("file open failure") ; } ~file() { if( std::fclose(m_file_handle) != 0 ) {} } void write( const char* str ) { if( std::fputs(str, m_file_handle) == EOF ) throw std::runtime_error("file write failure") ; } private: std::FILE* m_file_handle ; file( const file & ) ; file & operator=( const file & ) ; }; void funcion() { file logfile("logfile.txt") ; logfile.write("hello logfile!") ; }
Otro ejemplo para controlar memoria:
class mem { private: unsigned int size; char* data; public: mem (unsigned int size) { this->data = new char[size]; } ~mem () { delete [] this->data; } };
Consiste en crear una template no copiable que almacene las referencias a los objetos de una clase concreta, estos objetos serán eliminados cuando la clase sea eliminada. Es una forma de almacenar un conjunto de objetos en un contenedor, siendo este el encargado de su creación y eliminación librando al programador de esta tarea.
template <typename T> class RAIIFactory { private: vector<const T*> collection; // no copiable RAIIFactory (const RAIIFactory &); RAIIFactory & operator = (const RAIIFactory &); public: ~RAIIFactory () { while (!this->collection.empty()) { const T* tmp = this->collection.back(); this->collection.pop_back(); delete tmp; } } T* keep (T* tmp) { this->collection.push_back(tmp); return tmp; } RAIIFactory () {} };
Mediante la clase definida como RAIIFactory podemos crear Concrete RAIIFactories, estas utilizarían el método keep para almacenar en la RAIIFactory el (puntero al) objeto. Por ejemplo:
class MyClassFactory { private: RAIIFactory<MyClass> imp; public: MyClass* create () { return imp.keep(new MyClass()); } MyClass* create (int arg) { return imp.keep(new MyClass(arg)); } };
Ahora podemos hacer:
MyClassFactory mcf; MyClass* tmp = mcf.create(3);
La RAIIFactory no es para crear un solo objeto sino un número indeterminado, desconocido en tiempo de ejecución. La idea es que el objeto creado utilizando RAIIFactory se instancie por valor, de esa forma cuando salga de su ámbito (función, método…) los objetos se destruirán. Además no es un método de gestión de memoria intrusivo, no hay que cambiar las clases que almacenará para utilizarla.
Dos ampliaciones para RAIIFactory podrían ser:
Para la primera opción tenemos la siguiente implementación, en esta se elige la clase que pretendemos devolver (que ha de heredar de “MyBase”):
class RAIIPolymorphicFactory { public: template <typename T> T* create () { return (T*)imp.keep (new T ()); } private: RAIIFactory<MyBase> imp; }; // ... RAIIPolymorphicFactory mpf; MySecondClass* msc = mpf.create<MySecondClass>();
La segunda ampliación consiste en implementar tantos métodos create como deseemos:
template <typename T> class GFactory { public: T* create() { return imp.keep (new T); } template <typename A1> T* create (A1& a1) { return imp.keep (new T (a1)); } template <typename A1> T* create (const A1& a1) { return imp.keep (new T (a1)); } template <typename A1, typename A2> T* create (A1& a1, A2& a2) { return imp.keep (new T (a1, a2)); } template <typename A1, typename A2> T* create (const A1& a1, A2& a2) { return imp.keep (new T (a1, a2)); } template <typename A1, typename A2> T* create (A1& a1, const A2& a2) { return imp.keep (new T (a1, a2)); } // ...
Son los llamados punteros inteligentes, objetos que emulan ser punteros pero que se autogestionan a sí mismos (se inicializan y liberan cuando es necesario). En la STL nos encontramos con la clase a este tipo, la auto_ptr. Existen librerías como Boost o yasper que implementan su propia versión de puntero inteligente.
Un objeto auto_ptr se encarga de almacenar una instancia de la clase que sea y de destruirlo automáticamente cuando este se destruya. Su declaración es como sigue:
auto_ptr<tipoX> obj = new tipoX;
Se compartan como punteros ya que pueden aplicarsele los operadores * y ->, aún así su método de asignación es totalmente distinto, creando una copia del objeto que contienen
(mirar).
Una cosa a tener en cuenta es que no deben ser añadidos a colecciones, al menos los de la STL, su funcionamiento no sería correcto.