Gestión Memoria Memory Leaks
![Tuberías, fugas de memoria](/imagen/blog/memoryleaks/goteras.jpg)
Como bien sabemos los que programamos en C hay que ser muy cuidadosos con la gestión de memoria. Si creamos un espacio de memoria después debemos liberarnos.
En el IDE de VisualEstudio cuando finalizamos un programa en la ventana de la consola nos aparece el aviso de memory leaks cuando hemos creado memoria que no hemos liberado. Esto con el gdb no sucede, al menos yo no he sido capaz de que al finalizar un programa con gdb, me muestre algún tipo de aviso indicando que se ha quedado memoria sin liberar.
He creado la clase CMemoryLeaks, para detectar estos casos. Esta clase se implementa utilizando el patrón singleton.
Patrón singlenton
El patrón singleton, se define porque sólo se permite la creación de una única instancia (objeto) de una clase.
Este concepto se aplica, para que varios procesos (hilos, procesos) accedan a una única entidad. Por ejemplo a los datos/propiedades de un usuario, un dispositivo, como puede ser puerto serie, paralelo, usb, un disco duro, o como no la memoria. Por lo que cada vez que varios procesos instancien un objeto singleton, dichos procesos acceden a los mismos datos del objeto.
La característica principal de una clase con patrón singleton es que NUNCA debemos de instanciar o borrar directamente el objeto. Prohibido usar directamente new y delete.
Implementación de la clase CMemoryLeaks
Básicamente nuestra clase tiene un contenedor (hasmap) (std::map <UINT_PTR,std::string> mMap), que contiene tuplas de clave (dirección de memoria creada), dato (texto libre).
Cada vez que creemos memoria, le pasaremos la dirección de memoria y un texto, que insertará en el contenedor (hasmap).
Cuando liberemos memoria, le pasaremos la dirección de memoria que hemos liberado la buscará en el contenedor y borrará la tupla.
Primero definimos en la parte privada (prívate/protected) de la clase, la variable que contendrá la instancia de la clase CMemoryLeaks y el mutex.
CMemoryLeaks.h
static CMemoryLeaks *mspInstancia; static std::mutex msMutex;
En la implementación de la clase debemos de iniciar la instancia a null.
CMemoryLeaks.cpp
CMemoryLeaks* CMemoryLeaks::mspInstancia = nullptr; std::mutex CMemoryLeaks::msMutex;
Ahora definimos la función que creara una única instancia.
En la definición pública de la clase definimos la función getInstance como static.
CMemoryLeaks.h
public: static CMemoryLeaks *getInstance(void);
La implementación de la función
CMemoryLeaks.cpp
CMemoryLeaks *CMemoryLeaks::getInstance(void) { //bloqueamos el acceso a la parte de código critica. CMemoryLeaks::msMutex.lock(); if (CMemoryLeaks::mspInstancia== nullptr) { //no hay instancia, la creamos CMemoryLeaks::mspInstancia=new CMemoryLeaks(); } //desbloquemoas la parte del código crítica CMemoryLeaks::msMutex.unlock(); //devolvemos la instancia return CMemoryLeaks::mspInstancia; }
Ahora definimos la función que destruirá la instancia.
En la definición pública de la clase definimos la función finalizar como static. Esta función devolverá recorrerá el contenedor devolviendo los registros que no se hayan borrado, en un puntero de un string.
CMemoryLeaks.h
static std::string *finalizar(void);
La implementación de la función
CMemoryLeaks.cpp
std::string *CMemoryLeaks::finalizar(void) { std::map <UINT_PTR,std::string> lMap; std::map <UINT_PTR,std::string>::const_iterator lIter; //string donde dejaremos los registros std::string *lpstrAviso; lpstrAviso=NULL; if (CMemoryLeaks::mspInstancia!= nullptr) { lMap=CMemoryLeaks::mspInstancia->getMap(); if (lMap.size()!=0) {//tengo registros en el mapa for (lIter=lMap.begin();lIter!=lMap.end();lIter++ ) {//recorro todos los registros y los voy concatenando en el std::string if (lpstrAviso==NULL) lpstrAviso=new std::string(lIter->second); else lpstrAviso->append(lIter->second); //añado el retorno de carro lpstrAviso->append("\n"); }//fin for recorre los registros del mapa lMap.clear(); //borramos la instancia delete CMemoryLeaks::mspInstancia; CMemoryLeaks::mspInstancia= nullptr; }//fin tengo registros en el mapa } return lpstrAviso; }
Indicar la creación de memoria
Cuando creemos memoria deberemos de llamar a la función crear que se le pasarán 5 parámetros. Esta función no está definida como static.
Primero buscara por la dirección de memoria (clave) en el contenedor (mMap).
Si no lo encuentra inserta la tupla en el contenedor.
CMemoryLeaks.h
void crear(UINT_PTR pDirMem,std::string pstrFile, std::string pstrfuncion, unsigned int puiLine, std::string pstrInf);
La implementación de la función
CMemoryLeaks.cpp
void CMemoryLeaks::crear(UINT_PTR pDirMem, std::string pstrFile, std::string pstrfuncion, unsigned int puiLine, std::string pstrInf) { std::map <UINT_PTR,std::string>::const_iterator lIter; std::string lstr; //busco si existe un registro para la dirección de memoria (pDirMen) lIter=mMap.find(pDirMem); if (lIter==mMap.end()) {//no hay registro inserto un registro en el mapa //bloqueo el acceso al mapa. CMemoryLeaks::msMutex.lock(); lstr=pstrFile+":"+pstrfuncion+":"+std::to_string(puiLine)+": "+pstrInf; //inserto la tupla (dirección memoria(clave), texto(dato)) mMap.insert(std::map <UINT_PTR,std::string>::value_type(pDirMem,lstr)); //desbloqueo el acceso al mapa CMemoryLeaks::msMutex.unlock(); } }
Indicar liberación de memoria
Cuando liberemos la memoria deberemos de llamar a la función liberar al igual que crear no está definida como static, se le pasará la dirección de memoria que se libera.
Primero buscara por la dirección de memoria (clave) en el contenedor (mMap).
Si la encuentra borra la tupla del contenedor.
CMemoryLeaks.h
void liberar(UINT_PTR pDirMem);
La implementación de la función
CMemoryLeaks.cpp
void CMemoryLeaks::liberar(UINT_PTR pDirMem) { std::map <UINT_PTR,std::string>::const_iterator lIter; //busco si existe un registro para la dirección de memoria (pDirMen) lIter=mMap.find(pDirMem); if (lIter!=mMap.end()) {//hay registro inserto un registro en el mapa //bloqueo el acceso al mapa. CMemoryLeaks::sMutex.lock(); //borro la tupla encontrada mMap.erase(lIter); //desbloqueo el acceso al mapa CMemoryLeaks::sMutex.unlock(); } }
Es importante remarcar que el constructor, constructor de copia y el destructor de la clase están definidos dentro de la sección private/protected de la clase al igual que el operador de asignación (=).
De esta manera evitamos que podamos utilizar new, delete o = directamente sobre un objeto de clase CMemoryLeaks.
CMemoryLeaks.h
private: //constructor y destructor lo definimos en la parte privada, //para que no puedan ser utilizados CMemoryLeaks(void); ~CMemoryLeaks(void); //prohibimos el uso de los operadores constructor de copia y asignación CMemoryLeaks(CMemoryLeaks const&)=delete; CMemoryLeaks& operator=(CMemoryLeaks const&)=delete;
Como usar clase CMemoryLeaks
Vamos a hacer una aplicación en modo consola, que lanzará un hilo. Tanto el programa principal como el hilo utilizarán la clase CMemoryLeaks.
También habrá una función que crea memoria y devolverá un puntero del espacio de memoria creado.
Al final de la aplicación se llamará al método finalizar. Si dicho método devuelve un string, es que no hemos liberado correctamente la memoria.
Es un proyecto en eclipse, en un principio lo único que habrá que configurar es donde está el compilador de C/C++.
Menú -> Project -> C/C++ Build -> Environment.
En la variable MINGW_HOME
![Editar variable MINGW_HOME](/imagen/blog/memoryleaks/MINGW_HOME.png)
Se debe de indicar donde está el compilador de C/C++.
![Indicar ruta del compilador](/imagen/blog/memoryleaks/rutacompilador.png)
En un principio con esto ya se puede compilar el proyecto.
memoryLeaks.cpp
hilo (El hilo)
La función hilo, obtendrá una instancia de CMemoryLeaks.
lpContMem=CMemoryLeaks::getInstance();
Recordar que no podemos utilizar new, para crear una instancia de la clase CMemoryLeaks. Por eso utilizamos getInstance. Como hemos visto anteriormente, si ya existe una instancia de CMemoryLeaks devolverá la instancia, en caso contrario creará una instancia (objeto) y devolverá dicha instancia.
Creará memoria, en este caso un array de char de 6 elementos
lpchrPru=new char[6];
Indicará que se ha creado memoria.
lpContMem->crear((UINT_PTR)lpchrPru, __FILE__, __func__, __LINE__-1, "pchrPru=new char[6];");
Utilizará los identificadores del preprocesador ,__FILE__,__func__,__LINE__, para indicar en que fichero se ha creado la memoria, que función y en que línea se llama a la función crear, el quinto parámetro es un texto libre.
Esperará unos 5 segundos
sleep(5);
Función creaMemoryChar
Esta función simplemente creara un array de chars, con la longitud que se le pase como parámetro.
main (Programa principal)
La función main, lanzará el hilo
pthread_create(&lpthreadHilo,NULL,hilo,NULL);
Obtendrá una instancia de CMemoryLeaks.
lpContMemMain=CMemoryLeaks::getInstance();
Si ya existe una instancia de CMemoryLeaks devolverá la instancia, en caso contrario creará una instancia (objeto) y la devolverá.
Una vez lanzado el hilo. Se creará memoria para un integer
lpiPru=new int;
Se indicará que hemos creado memoria.
lpContMem->crear((UINT_PTR) lpiPru, __FILE__, __func__, __LINE__-1, "lpiPru=new int;");
Se llamara a la función creaMemoryChar para que cree un array de 10 elementos de chars;
Se indicará que la función crearMemoryChar ha creado memoria
lpContMemMain->crear((UINT_PTR)lpchrPru,__FILE__,__func__,__LINE__-1, "creaMemoryChar(10);");
lpstrError=CMemoryLeaks::finalizar();
Si lpstrError es diferente de NULL se mostrará los registros de la memoria que no se han liberado correctamente.
if (lpstrError!=NULL) { puts("Main-> no se ha gestionado la memoria correctamente"); puts(lpstrError->c_str()); delete lpstrError; }
![Mensajes de fuga de memoria](/imagen/blog/memoryleaks/salidamemoryleaks.png)
Podemos ver que no hemos liberado correctamente la memoria.
Indicando que fichero, función y línea donde se ha creado memoria, pero que dicha memoria no ha sido liberada.
Main-> no se ha gestionado la memoria correctamente ..\memoryLeaks.cpp:main:87: creaMemoryChar(10); ..\memoryLeaks.cpp:main:75: lpiPru=new int; ..\memoryLeaks.cpp:hilo:32: pchrPru=new char[6];
Si por ejemplo liberamos memoria correctamente en el hilo.
memoryLeaks.cpp
hilo (El hilo)
Descomentando las siguientes líneas.
lpContMem->liberar((UINT_PTR)lpchrPru); delete []lpchrPru; lpchrPru=NULL;
![Salida sin fugas de memoria](/imagen/blog/memoryleaks/salidasinmemoryleaks.png)
Podemos ver como ya no aparece el registro del hilo (..\memoryLeaks.cpp:hilo:32: pchrPru=new char[6]; ) y sólo aparecen los registros de la creación de la memoria del main y de la función crearMemoryChar.
Main-> no se ha gestionado la memoria correctamente ..\memoryLeaks.cpp:main:87: creaMemoryChar(10); ..\memoryLeaks.cpp:main:75: lpiPru=new int;
Evidentemente si cuando creamos o cuando liberamos memoria no se lo indicamos a la clase, puede darnos información incorrecta.
Código
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
memoryLeaks | memoryleaks.zip | 2024-07-14 | 8170 bytes | 66 |