cpp-new-y-delete

Qué es new y delete en C++

Para gestionar la memoria dinámica, C++ proporciona dos operadores new y delete para reservar y liberar memoria, respectivamente.

En C++, la memoria se puede gestionar de dos maneras principales:

  • Memoria automática: Se asigna automáticamente cuando se declara una variable local o global y se libera automáticamente cuando la variable sale de su ámbito.

  • Memoria dinámica: Se asigna y se libera manualmente por el programador durante la ejecución del programa utilizando los operadores new y delete.

Básicamente, la memoria dinámica es espacio de almacenamiento que necesitamos, pero no sabemos cuanto necesitamos hasta tiempo de ejecución (por ejemplo, porque depende de una entrada del sistema).

A diferencia de la memoria automática, la memoria asignada con new persiste hasta que se libera explícitamente mediante delete.

Qué es new

El operador new en C++ se utiliza para reservar memoria dinámica (es decir, para decirle al sistema “eh!, dame un sitio donde guardar esto”).

tipo_dato* puntero = new tipo_dato;
  • tipo_dato: Es el tipo de dato para el cual se está asignando memoria.
  • puntero: Es un puntero que almacenará la dirección de la memoria asignada.

Vamos a verlo con un ejemplo,

int* p = new int; // Asigna memoria para un entero
*p = 5;           // Almacena el valor 5 en la memoria asignada

std::cout << *p << std::endl; // Imprime 5

En este ejemplo,

  • Se asigna memoria para un entero
  • El puntero p apunta a esa memoria
  • Se almacena el valor 5 en esa ubicación de memoria.
  • A través de él podemos acceder al valor almacenado.

Asignación de arrays dinámicos

El operador new también puede utilizarse para asignar memoria para arrays dinámicos. La sintaxis es ligeramente diferente:

tipo_dato* puntero = new tipo_dato[tamaño];

Es decir, básicamente es igual, pero ponemos [] indicando el tamaño de la variable a reservar. Vamos a verlo con un ejemplo,

int* arr = new int[5]; // Asigna memoria para un array de 5 enteros

for(int i = 0; i < 5; ++i) {
    arr[i] = i * 2; // Inicializa el array con valores
}

for(int i = 0; i < 5; ++i) {
    std::cout << arr[i] << " "; // Imprime los valores del array
}

std::cout << std::endl;

Este ejemplo muestra cómo asignar y usar un array dinámico. Aquí, arr es un puntero a un bloque de memoria que puede contener 5 enteros.

Qué es delete

El operador delete se utiliza para liberar la memoria que fue asignada dinámicamente con new.

delete puntero;
  • puntero: Es el puntero que apunta a la memoria que debe ser liberada.

Vamos a verlo con un ejemplo

int* p = new int; // Asigna memoria para un entero
*p = 5;           // Almacena el valor 5 en la memoria asignada

delete p;         // Libera la memoria
p = nullptr;      // Evita que el puntero apunte a memoria liberada

En este ejemplo,

  • Después de utilizar la memoria asignada, se libera con delete
  • Se asigna nullptr al puntero para evitar que apunte a una dirección de memoria que ya no es válida.

Liberación de arrays dinámicos

Cuando se asigna memoria para un array dinámico, es importante liberar esa memoria correctamente utilizando el operador delete[].

delete[] puntero;

Por ejemplo

int* arr = new int[5]; // Asigna memoria para un array de 5 enteros

for(int i = 0; i < 5; ++i) {
    arr[i] = i * 2;
}

// Uso del array

delete[] arr; // Libera la memoria del array
arr = nullptr; // Previene acceso a memoria liberada

En este ejemplo, la memoria asignada para el array se libera utilizando delete[].

Es importante usar delete[] en lugar de delete para evitar comportamientos indefinidos (a.k.a que te pete algo)

Errores comunes al usar new y delete

El error más habitual es olvidarnos de hacer un delete cuando ya no necesitamos una variable, o cuando vamos a volver a reservarla.

void fugaMemoria() {
    int* p = new int; // Asigna memoria para un entero
    // Se olvida liberar la memoria
    
	p = new int; // Volvermos a reservar memoria
}

En este caso, el puntero p se destruye al salir de la función, pero la memoria asignada no se libera, lo que provoca una fuga.

A esto se le llama fuga de memoria (memory leak) y es un error muy habitual.


Esto lleva a que el programa vaya consumiendo más y más memoria y, eventualmente, haga catapum 💥 (y además es una guarrada como programadores).

La memoria se liberará en todo caso al cerrar el programa. No os penséis que es una cosa permanente al sistema.

Otro error habitual es acceder a memoria que ya ha sido liberada con delete, y que igualmente va a ocasiones un error en tu programa.

int* p = new int(10);
delete p;   // Libera la memoria

*p = 5;     // Error: acceso a memoria liberada

Este ejemplo muestra un acceso a memoria después de que ha sido liberada, lo que puede causar un otro catapum de tu programa 💥.

Liberar la misma memoria más de una vez es otro error común, conocido como doble liberación.

void dobleLiberacion() {
    int* p = new int(10);
    delete p;
    
    delete p; // Error: liberación doble
}

En este caso, se intenta liberar la misma memoria dos veces, lo cual es un error grave.

Buenas prácticas