Manejo de memoria en C++

Anuncio
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
Descargar