Herramientas de usuario

Herramientas del sitio


highlevel:c:advancing

¡Esta es una revisión vieja del documento!


Avanzando en C++

Gestión de memoria

Memory Leak

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

Funciones en Windows

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”.

  • bool _CrtDumpMemoryLeaks(), muestra una lista de los memory leaks existentes, devuelve TRUE o FALSE según si ha encontrado o no. Si se hace un #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:

  • void _CrtMemCheckpoint (_CrtMemState*) que guarda información del estado actual del heap en esa _CrtMemState.
  • bool _CrtMemDifference (_CrtMemState*, _CrtMemState*, _CrtMemState*) devuelve TRUE si la segunda y la tercera _CrtMemState son diferentes, en la primera guardará las diferencias.
  • void _CrtMemDumpStatistics (_CrtMemState*) dada una _CrtMemState muestra su información. Un buen uso sería el ejemplo que sigue, para que todo esté correcto debe mostrar que 0 bytes reservados en bloques normales.
_CrtMemState s1, s2, s3;
_CrtMemCheckpoint( &s1 ); 
// código
_CrtMemCheckpoint( &s2 ); 
if ( _CrtMemDifference( &s3, &s1, &s2) ) 
  _CrtMemDumpStatistics( &s3 );
  • void _CrtMemDumpAllObjectsSince (_CrtMemState*) muestra información sobre todos los objetos que se han asignado desde que se hizo el snapshot sobre el _CrtMemState dado(o desde el principio de la ejecución si se pasa NULL).

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; 
} 

RAII

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;
	}
};

RAII-Factory

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:

  1. Que pudiese tratar con clases derivadas.
  2. Que pudiese crear clases a partir de tantos parámetros de constructor como sean necesarios.

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)); }
// ...

Smart Pointers

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.

Bibliotecas (Libraries)

Una biblioteca es una colección de código que permite hacer dicho código independiente del programa en el que se añaden, esto hace que el código pueda ser estructurado y reutilzado de forma modular. Existen dos tipos de bibliotecas:

  • Estáticas, se añaden al programa, en el mismo archivo, en tiempo de compilación.
  • Dinámicas, se cargan y se enlazan al programa en el momento en el que este lo necesita, durante el tiempo de ejecución. En sistemas Windows son llamadas DLL y pueden tener como extensión .dll, .ocx (si incluyen un control ActiveX) o .drv (si pertenecen a un driver).

Tips para crear bibliotecas desde Visual Studio

Crear una biblioteca (Windows)

Cuando se va a utilizar una librería dinámica en un programa es necesario que se proporcionen dos archivos, un .h (mediante un #include) y un .lib (esta debe ser vinculada desde el compilador), en el primero estará el código de dicha biblioteca y en el segundo un mapeo al archivo .dll.
Al crear la librería hay que tener en cuenta las siguientes cosas:

  • En el archivo .h del código correspondiente a la biblioteca añadiremos extern "C" _declspec(dllexport) antes de cada función que queramos que sea utilizable en la biblioteca.
  • El archivo .h que se añadirá al código del programa debe de contener extern "C" _declspec(dllimport) delante de cada declaración de función.
/*************** Proyecto Librería **********************/
// lib.h 
extern "C" _declspec(dllexport) int suma (int);

// lib.cpp
#include "lib.h"
int suma (int a) {
	return a + 4;
}

/*************** Proyecto Ejecutable **********************/
// bin.h
extern "C" _declspec(dllimport) int suma (int);

// bin.cpp
#include <iostream>
#include <stdlib.h>
#include "lib.h"

void main () {
	std::cout << suma(33) << std::endl;
	system("pause");
}
  • La parte del extern "C" no es necesaria en algunos compiladores.
  • Respecto a importar\exportar clases la sintaxis es: class _declspec(dllimport) nombreClase. En la cabecera del ejecutable no sería necesario añadir los privates.
/*************** Proyecto Librería **********************/
// lib.h 
class _declspec(dllimport) A {
private:
	int suma (int i);
public:
	int haz (int i);
};
 
/*************** Proyecto Ejecutable **********************/
// bin.h
// lib.h 
class _declspec(dllimport) A {
public:
	int haz (int i);
};
highlevel/c/advancing.1211999639.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)