Memoria Dinamica: new, delete y Sus Peligros
Aprende como funciona la asignacion dinamica con new y delete, comprende los peligros de las fugas de memoria, doble liberacion y uso despues de liberar, y descubre por que el C++ moderno no deberia contener ningun new/delete crudo.
Asignacion Dinamica con new y delete
La pila es rapida pero limitada en tamano y ambito. Cuando necesitas objetos que sobrevivan al ambito que los creo, o cuyo tamano es desconocido en tiempo de compilacion, asignas en el monton (almacen libre).
new T(args) asigna memoria para un T, llama a su constructor y retorna un T*. delete p llama al destructor y libera la memoria. Para arrays: new T[n] y delete[] p. Mezclar new con delete[] o new[] con delete es comportamiento indefinido — el compilador no detectara esto.
new, delete y Sus Variantes para Arrays
Aqui esta la API de asignacion cruda. Estudiala para reconocerla en codigo legado, pero nunca escribas esto en codigo nuevo — usa punteros inteligentes (smart pointers) en su lugar.
#include <iostream>
#include <cstring>
struct Widget {
int id;
Widget(int i) : id(i) { std::cout << "Widget " << id << " created\n"; }
~Widget() { std::cout << "Widget " << id << " destroyed\n"; }
};
int main() {
// Single object
Widget* w = new Widget(1);
std::cout << "Using widget " << w->id << '\n';
delete w; // destructor called, memory freed
// Array
Widget* arr = new Widget[3]{{10}, {20}, {30}};
for (int i = 0; i < 3; ++i)
std::cout << arr[i].id << ' ';
std::cout << '\n';
delete[] arr; // MUST use delete[] for new[]
// UNDEFINED BEHAVIOR — don't do these:
// delete arr; // wrong: used delete instead of delete[]
// delete[] w; // wrong: used delete[] instead of delete
}Placement new
Placement new construye un objeto en una direccion de memoria especifica sin asignar. Es usado por asignadores, pools de memoria y contenedores como std::vector internamente. Debes llamar al destructor explicitamente — delete NO debe usarse en objetos creados con placement new.
#include <iostream>
#include <new> // required for placement new
#include <cstddef> // std::byte
#include <memory> // std::align
struct Sensor {
int id;
double reading;
Sensor(int i, double r) : id(i), reading(r) {
std::cout << "Sensor " << id << " init\n";
}
~Sensor() { std::cout << "Sensor " << id << " shutdown\n"; }
};
int main() {
// Pre-allocate raw storage (aligned)
alignas(Sensor) std::byte buffer[sizeof(Sensor)];
// Construct in pre-allocated memory
Sensor* s = new (buffer) Sensor(42, 3.14);
std::cout << "Reading: " << s->reading << '\n';
// Must destroy explicitly — do NOT use delete
s->~Sensor(); // explicit destructor call
// delete s; // WRONG — memory was not allocated by new
}Los Tres Errores Mortales de Memoria
Los new/delete crudos te exponen a tres categorias de errores:
1. Fuga de Memoria (Memory Leak) — asignar con new pero nunca llamar a delete. La memoria nunca se devuelve al sistema operativo (hasta que el proceso termina). En un servidor de larga ejecucion, esto eventualmente consume toda la RAM disponible.
2. Doble Liberacion (Double Free) — llamar a delete en el mismo puntero dos veces. El segundo delete corrompe las estructuras de datos internas del asignador del monton, frecuentemente causando fallos mucho despues en codigo no relacionado. Esto es extremadamente dificil de depurar.
3. Uso Despues de Liberar (Use-After-Free) — acceder a memoria despues de delete. La memoria puede haber sido reutilizada para una asignacion diferente, asi que podrias estar leyendo o escribiendo datos de otro objeto. Esta es una vulnerabilidad de seguridad (error de clase CVE).
Considera: f(new A(), new B()). El compilador puede evaluar los argumentos en cualquier orden y puede intercalarlos:
1. Asignar memoria para A
2. Asignar memoria para B
3. Construir A
4. Construir B
Si el paso 4 lanza una excepcion, A tiene una fuga — nadie llama a delete sobre ella. Incluso f(std::shared_ptr(new A()), std::shared_ptr(new B())) tenia este problema antes de C++17 (que garantiza que cada argumento se evalua completamente antes del siguiente). La solucion: siempre usa std::make_unique o std::make_shared — realizan la asignacion y construccion como una unica operacion segura ante excepciones.
Vision General de Asignadores Personalizados
Los new/delete por defecto usan el asignador global (tipicamente malloc/free), que es de proposito general pero no optimo para todos los patrones. Los asignadores personalizados (custom allocators) proporcionan:
- Asignadores de arena/lineales — avanzan un puntero, liberan todo de una vez. Excelentes para datos por cuadro en juegos.
- Asignadores de pool — pre-asignan bloques de tamano fijo. Asignacion/liberacion O(1), sin fragmentacion.
- Asignadores de pila — asignacion LIFO, extremadamente rapida.
Los contenedores de C++ aceptan parametros de plantilla de asignador: std::vector. El espacio de nombres std::pmr (recursos de memoria polimorficos) en C++17 proporciona un framework de asignadores polimorfico en tiempo de ejecucion que evita el problema del parametro de plantilla.
En C++ moderno (C++14 y posterior), el codigo de aplicacion bien escrito deberia contener cero new y delete crudos:
- Propiedad exclusiva: usa std::make_unique
- Propiedad compartida: usa std::make_shared
- Arrays dinamicos: usa std::vector
- Cadenas: usa std::string
- Objetos opcionales: usa std::optional
Los new/delete crudos pueden aparecer en codigo de biblioteca (asignadores, contenedores personalizados), pero el codigo de aplicacion nunca deberia necesitarlos. Si ves new en una revision de codigo, pregunta por que — casi siempre hay una alternativa mas segura.
- Mezclar
new/delete[]onew[]/deletees comportamiento indefinido - Las fugas de memoria, doble liberacion y uso despues de liberar son los tres errores mortales de memoria
f(new A(), new B())puede tener fugas si un constructor lanza una excepcion (antes de C++17 incluso con wrappers de shared_ptr)- Placement new construye en una direccion dada — destruye manualmente, nunca uses
delete - El codigo de aplicacion en C++ moderno deberia tener cero
new/deletecrudos — usa punteros inteligentes y contenedores
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Que sucede si usas `delete` en memoria asignada con `new[]`?
2. Por que `f(new A(), new B())` puede tener fugas de memoria?
3. Como deberias destruir un objeto creado con placement new?