Dynamic Memory: new, delete & Their Dangers
Learn how dynamic allocation works with new and delete, understand the dangers of memory leaks, double-free, and use-after-free, and discover why modern C++ should contain zero raw new/delete.
Dynamic Allocation with new and delete
The stack is fast but limited in size and scope. When you need objects that outlive their creating scope, or whose size is unknown at compile time, you allocate on the heap (free store).
new T(args)allocates memory for oneT, calls its constructor, and returns aT*.delete pcalls the destructor and frees the memory. For arrays:new T[n]anddelete[] p. Mismatchingnewwithdelete[]ornew[]withdeleteis undefined behavior — the compiler will not catch this.
new, delete și variantele lor de matrice
Here is the raw allocation API. Study it so you recognize it in legacy code, but never write this in new code — use smart pointers instead.
#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 constructs an object at a specific memory address without allocating. It is used by allocators, memory pools, and containers likestd::vectorinternally. You must call the destructor explicitly —deletemust NOT be used on placement-new'd objects.
#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
}Cele trei erori mortale de memorie
Rawnew/deletevă expune la trei categorii de erori:
1. Scurgere de memorie — se alocă cunew, dar nu se apelează niciodată ladelete. Memoria nu este niciodată returnată la sistemul de operare (până la ieșirea procesului). Într-un server cu funcționare lungă, acesta consumă în cele din urmă toată memoria RAM disponibilă.
2. Double Free — apelareadeletepe același indicator de două ori. Al doileadeletecorupă structurile interne de date ale alocătorului de heap, ducând adesea la blocări mult mai târziu în codul care nu are legătură. Acest lucru este extrem de greu de depanat.
3. Use-After-Free — accesarea memoriei dupădelete. Este posibil ca memoria să fi fost reutilizată pentru o alocare diferită, astfel încât ați putea citi sau scrie datele unui alt obiect. Aceasta este o vulnerabilitate de securitate (bun de clasă CVE).
Luați în considerare:f(new A(), new B()). Compilatorul poate evalua argumentele în orice ordine și le poate intercala:
1. Alocați memorie pentru A
2. Alocați memorie pentru B
3. Construcția A
4. Construcția B
Dacă pasul 4 este aruncat, A este scurs - nimeni nu sunădeletepe el. Chiar șif(std::shared_ptr(new A()), std::shared_ptr(new B()))a avut această problemă înainte de C++17 (ceea ce garantează că fiecare argument este pe deplin evaluat înainte de următorul). Remedierea: folosiți întotdeaunastd::make_uniquesaustd::make_shared - efectuează alocarea și construcția ca o singură operație, sigură pentru excepții.
Prezentare generală a alocătorilor personalizați
new/deleteimplicit utilizează alocatorul global (de obiceimalloc/free), care este de uz general, dar nu este optim pentru toate modelele. Alocatorii personalizați furnizează:
- Alocatori arenei/liniari — loviți un indicator, eliberați totul deodată. Excelent pentru date de joc pe cadru.
- Alocatoare de piscine — pre-alocați blocuri de dimensiuni fixe. O(1) aloc/liber, fără fragmentare.
- Alocatoare de stivă — Alocare LIFO, extrem de rapidă.
Containerele C++ acceptă parametrii șablonului de alocare:std::vector. Spațiul de numestd::pmr(resurse de memorie polimorfă) din C++17 oferă un cadru de alocare polimorf de rulare care evită problema parametrilor șablonului.
In modern C++ (C++14 and later), well-written application code should contain zero rawnewanddelete:
- Single ownership: usestd::make_unique
- Shared ownership: usestd::make_shared
- Dynamic arrays: usestd::vector
- Strings: usestd::string
- Optional objects: usestd::optional
Rawnew/deletemay appear in library code (allocators, custom containers), but application code should never need them. If you seenewin a code review, ask why — there is almost always a safer alternative.
- Nepotrivirea
new/delete[]saunew[]/deleteeste un comportament nedefinit - Scurgerile de memorie, dublu-free și use-after-free sunt cele trei erori mortale de memorie
f(new A(), new B())poate scurge dacă un constructor aruncă (pre-C++17 chiar și cu pachetele shared_ptr)- Plasați noi constructe la o anumită adresă - distrugeți manual, nu utilizați niciodată
delete - Codul modern al aplicației C++ ar trebui să aibă zero
new/deletebrut - utilizați indicatori și containere inteligente
Test — Verifică-ți cunoștințele
(15 XP)1. Ce se întâmplă dacă utilizați`delete`pe memoria alocată cu`new[]`?
2. Why can`f(new A(), new B())`leak memory?
3. Cum ar trebui să distrugi un obiect creat cu plasare nouă?