Uno de los aspectos más importantes en todo lenguaje de programación es cómo se gestiona la memoria. Es decir cuándo, cómo y quién libera los objetos que se van creando durante la ejecución del programa. De hecho podemos detectar cuatro grandes técnicas de gestión de la memoria: gestión manual, garbage collector, contador de referencias manual y contador de referencias automático. Cada una de ellas tiene sus casuísticas las cuales veremos, de forma resumida, en sucesivos posts.
Gestión manual
La gestión manual de memoria es la que tienen lenguajes como C o C++ clásico. En este modelo toda la responsabilidad se deja el desarrollador. Sus defensores afirman que la gestión de memoria es tan importante que no puede ser dejada al sistema y debe efectuarla el desarrollador.
Por ejemplo, en C++ se usa la palabra clave new para crear un objeto y obtener un puntero que lo apunte y se usa delete para destruir el objeto:
class Beer {
public: std::string name;
};
int _tmain(int argc, _TCHAR* argv[])
{
auto myBeer = new Beer();
myBeer->name = "Voll Damm";
// más código
delete myBeer;
myBeer->name = "Heineken";
return 0;
}
Este código crea un objeto Beer mediante new, opera con él y finalmente lo elimina (mediante delete). Al eliminar el objeto el puntero myBeer sigue apuntando a la misma dirección de memoria, pero ahora ya no contiene el objeto. Es lo que se conoce como un dangling pointer. Acceder a un dangling pointer (como en la línea posterior a delete) nunca trae buenas noticias:
En el ejemplo anterior el error se observa muy claramente, pero cuando tenemos más de un puntero accediendo al mismo objeto entonces las cosas se complican: debemos usar delete cuando ya no vayamos a usar nunca más el objeto, desde ninguno de sus punteros. Al pasar punteros como parámetros es cuándo empiezan las dudas:
auto myBeer = new Beer();
myBeer->name = "Voll Damm";
drink(myBeer);
Si la función drink llama a delete del puntero pasado entonces, una vez llamada ya no podemos acceder más al objeto usando myBeer, ni podemos volver a usar delete (eliminar dos veces un objeto es otro error fatal). Pero si drink no llama a delete entonces debemos hacerlo nosotros.
Y por supuesto si no llamamos nunca a delete entonces tenemos un memory leak. Eso implica que al usar gestión manual debemos tener muy claro quién crea qué, quién usa qué y hasta cuándo y quién destruye qué. Y eso no siempre es fácil.
En posteriores partes de este artículo iremos desgranando los diferentes métodos de gestión de la memoria en diversos lenguajes. ¡No te los pierdas!
Nota: Imagen de cabecera "Writing tools by Pete O'shea" usaba bajo licencia CC