Manejo de memoria en C++ Memoria en C++ • La memoria de una aplicación está dividida en dos áreas La pila o Stack El montículo o Heap Programación Orientada a Objeto Ing. Civil en Telecomunicaciones Usando la pila Usando la pila • Cada vez que se crea un bloque, una función o un lazo iterativo se crea un nuevo marco en la pila (stack frame) Marcos proveen un espacio de memoria aislado que define el alcance de una variable en un bloque “vive” sólo en ese bloque, sobrecarga otras variables existentes del mismo nombre y desaparece cuando el bloque termina su ejecución Variable declarada Creando vectores en la pila Pila Heap miVector[0] int miVector[6]; miVector[1] miVector es una estructura estática creada en la pila, y su tamaño es conocido al momento de compilar el programa miVector[2] miVector[3] miVector[4] miVector[5] ©2015 Mario Medina C. • Las variables locales, variables temporales, estructuras de datos y objetos estáticos se crean en la pila • Los objetos creados dinámicamente se crean en el heap int main () { int i = 10, j = 5; cout << "i = " << i << endl; for (int i = 20; i < 21; i++) cout << "i = " << i << endl; { int i = 30; cout << "i = " << i << endl; } cout << "i = " << i << endl; return 0; } // Imprime 10 // Imprime 20 // Imprime 30 // Imprime 10 Usando el heap • El montículo o heap es un área de memoria independiente de la función en ejecución Variables en el heap persisten una vez terminada la función en ejecución, y deben ser eliminadas explícitamente por el programa una vez que ya no son necesarias Si no se libera la memoria, ésta se pierde para el resto del programa! 1 Usando el heap Usando el heap mediante punteros • Objetos y estructuras de datos creados dinámicamente se almacenan en el heap • Declaración de puntero no inicializado En C, se usa malloc() y calloc() para solicitar memoria en el heap, y free() para liberar la memoria En C++, se usa new para crear objetos en el heap, y delete para destruirlos int *miDato; Apunta a posición aleatoria en memoria • Recomendable declarar e inicializar puntero al mismo tiempo Por último, inicializarlo a nullptr int *miDato = nullptr; Usando nullptr • En C y C++03, la constante 0 se usa para Representar un número 0 Representar un puntero a NULL • En C++11, se introduce el comando nullptr para evitar confusiones Usando nullptr void funcion(char *s) { cout << ”version char*” << endl; } void funcion(int i) { cout << ”version int” << endl; } int main () { // Cual de las dos funciones es invocada? funcion(NULL); funcion(nullptr); // Aqui esta claro! } Usando el heap mediante punteros Creando vectores en el heap • Creacion de objetos mediante new • Es posible crear vectores en forma dinámica en el heap usando el operador new[] int *miDato = new int; Puntero contiene la dirección del objeto • Acceso al dato almacenado en el puntero *miDato = 8; • Destrucción del objeto delete miDato; miDato = nullptr; ©2015 Mario Medina C. int n = 6; int *miVector = new int[n]; El puntero reside en la pila, pero el vector mismo reside en el heap El vector debe ser destruido mediante delete[] miVector; 2 Creando vectores en el heap Pila int miVector miVector es una estructura dinámica creada en la pila, y su tamaño es conocido sólo al momento de ejecutar el programa Heap miVector[0] miVector[1] miVector[2] miVector[3] • Todo objeto creado con new debe ser destruido con delete • Todos los objetos creados con new[] deben ser destruidos con delete[] • Si esto no ocurre, hay una fuga de memoria (memory leak) miVector[4] No mezclar new con delete[] y viceversa! miVector[5] No destruir un objeto más de una vez! Función malloc() y comando new • Nunca usar malloc() y free() con objetos! Obj* miObj = (Obj *) malloc(sizeof(Obj)); Obj* miObj = new Obj(); • malloc() sólo reserva memoria • new crea un objeto llamando al constructor de ese objeto Asimismo, free() libera la memoria, pero delete llama al destructor Fugas de memoria • En el ejemplo anterior, el comando new reserva memoria en el heap Pero, esa memoria no es liberada en la función f1, sino que es asignada al puntero c en la función invocadora • Esa función es responsable por liberar la memoria • Qué pasa si no lo hace? La memoria asignada se “pierde” ©2015 Mario Medina C. Comandos new y delete Fugas de memoria int* f1(int a, int b) { int *temp = new int; temp = a + b; return temp; } En la pila En el heap int *c; c = f1(10, 20); Fugas de memoria void f2() { Objeto *o = new Objeto; Objeto->metodo(); delete Objeto; } • Esta función parece estar OK, pero qué pasa si metodo() genera una excepción? delete nunca se ejecuta, así que hay una fuga de memoria 3 Punteros inteligentes (smart ptrs) unique_ptr • Cómo evitar las fugas de memoria? Con punteros que sepan cuándo un objeto creado dinámicamente ya no será utilizado • Similar a un puntero normal de C, pero automáticamente libera la memoria asignada al puntero cuando este puntero sale del alcance ó es destruido • Un unique_ptr es el único dueño del objeto al que apunta • Recomendable unique_ptr usar siempre en vez de punteros simples C++03 usaba punteros auto_ptr, los que están obsoletos porque no operan correctamente con la STL • C++11 introduce unique_ptr, shared_ptr y weak_ptr Declarados en <memory> unique_ptr • Para crear un unique_ptr, se usa unique_ptr<int> up_temp(new int); • A partir de C++14, se puede usar auto up_temp = make_unique<int>(); unique_ptr puede apuntar a cualquier tipo de objeto, por lo que es una plantilla unique_ptr unique_ptr<int> f1(int a, int b) { unique_ptr<int> *temp = new int; temp = a + b; En el heap return temp; } En la pila unique_ptr<int> c = f1(10, 20); // Puntero se mueve, no se copia! Constructores de copia y move shared_ptr • Punteros unique_ptr no permite asignaciones ni tienen constructor de copia, pero sí permiten operaciones move • Similar a un unique_ptr, pero para objetos que son compartidos • Mantiene un contador de referencias al objeto unique_ptr<int> p1(new int); unique_ptr<int> p2 = p1; // ERROR unique_ptr<int> p3 = move(p1); // OK // Ahora p1 es nullptr // p3 es el dueño del entero ©2015 Mario Medina C. Se incrementa cada vez que se define un nuevo puntero al objeto Se decrementa cada vez que un puntero al objeto se destruye o sale de alcance Destruye el objeto cuando el contador es 0 4 shared_ptr • Para crear un shared_ptr, se usa shared_ptr<int> sp_temp(new int); • También se puede usar auto up_temp = make_shared<int>(sp_temp); make_shared está incluido en C++11 Declarado en <memory> Doble destrucción de objetos Class Obj { public: Obj() { cout << “Crea un Obj” << endl; } virtual ~Nada() { cout << “Destruye un Obj” << endl; } } { // Bloque Obj miObj = new Obj(); shared_ptr<Obj> p1(miObj); shared_ptr<Obj> p2(miObj); } // Fin del bloque Doble destrucción de objetos weak_ptr • Al terminar la ejecución del bloque, el objeto se destruye dos veces! • Similar a un shared_ptr, apunta a objetos que son compartidos • No modifica el contador de referencias al objeto Ése es el comportamiento correcto de acuerdo al estándar • Para copiar punteros shared_ptr, usar el constructor de copia shared_ptr<Obj> p1(miObj); shared_ptr<Obj> p2(p1); ©2015 Mario Medina C. Sólo permite observar el objeto apuntado por el puntero La destrucción del weak_ptr no causa la destrucción del objeto 5