Dynamická paměť: nová, smazat a jejich nebezpečí
Zjistěte, jak dynamická alokace funguje s novými a odstraněnými, pochopte nebezpečí úniků paměti, double-free a use-after-free, a zjistěte, proč by moderní C++ měl obsahovat nula raw new/delete.
Dynamická alokace s novými a smazáním
Stack je rychlý, ale omezený co do velikosti a rozsahu. Když potřebujete objekty, které přežijí svůj rozsah vytváření nebo jejichž velikost je v době kompilace neznámá, alokujete je na hromadě (volné úložiště).
new T(args)alokuje paměť pro jedenT, zavolá jeho konstruktor a vrátíT*.delete pzavolá destruktor a uvolní paměť. Pro pole:new T[n]adelete[] p. Neshodanewsdelete[]nebonew[]sdeleteje nedefinované chování — kompilátor to nezachytí.
new, delete a Jejich varianty pole
Zde je nezpracované rozhraní API. Prostudujte si to, abyste to poznali ve starším kódu, ale nikdy to nepište do nového kódu – místo toho použijte chytré ukazatele.
#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
}Umístění nové
Umístění nové vytvoří objekt na konkrétní adrese paměti bez alokace. Interně jej používají alokátory, paměťové fondy a kontejnery jakostd::vector. Destruktor musíte volat explicitně —deleteNESMÍ být použit na objekty nové umístění.
#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
}Tři smrtící chyby v paměti
Rawnew/deletevás vystavuje třem kategoriím chyb:
1. Únik paměti — alokace pomocínew, ale nikdy volánídelete. Paměť se nikdy nevrátí do operačního systému (až do ukončení procesu). Na dlouho běžícím serveru to nakonec spotřebuje veškerou dostupnou RAM.
2. Double Free — volánídeletena stejném ukazateli dvakrát. Druhýdeletepoškodí vnitřní datové struktury alokátoru haldy, což často vede k selháním mnohem později nesouvisejícího kódu. Toto je extrémně těžké ladit.
3. Use-After-Free — přístup k paměti podelete. Paměť mohla být znovu použita pro jinou alokaci, takže můžete číst nebo zapisovat data jiného objektu. Toto je bezpečnostní chyba (chyba třídy CVE).
Zvažte:f(new A(), new B()). Kompilátor může vyhodnocovat argumenty v jakémkoli pořadí a může je prokládat:
1. Přidělte paměť pro A
2. Přidělte paměť pro B
3. Konstrukce A
4. Konstrukce B
Pokud hodí krok 4, A unikne — nikdo na něm nevoládelete. Dokonce if(std::shared_ptr(new A()), std::shared_ptr(new B()))měl tento problém před C++17 (což zaručuje, že každý argument bude plně vyhodnocen před dalším). Oprava: vždy používejtestd::make_uniquenebostd::make_shared – provádějí alokaci a konstrukci jako jedinou operaci bezpečnou pro výjimky.
Přehled vlastních alokátorů
Výchozínew/deletepoužívá globální alokátor (typickymalloc/free), který je obecný, ale není optimální pro všechny vzory. Vlastní alokátory poskytují:
- Aréna/lineární alokátory – narazíte na ukazatel a uvolníte vše najednou. Skvělé pro herní data na jeden snímek.
- Alokátory fondu – předem přidělují bloky pevné velikosti. O(1) alloc/free, žádná fragmentace.
- Alokátory zásobníku — alokace LIFO, extrémně rychlá.
Kontejnery C++ přijímají parametry šablony alokátoru:std::vector. Jmenný prostorstd::pmr(polymorfní paměťové prostředky) v C++17 poskytuje runtime-polymorfní alokační rámec, který se vyhýbá problému s parametry šablony.
V moderním C++ (C++14 a novějších) by měl dobře napsaný kód aplikace obsahovat nulové nezpracovanénewadelete:
- Jediné vlastnictví: použijtestd::make_unique
- Sdílené vlastnictví: použijtestd::make_shared
- Dynamická pole: použijtestd::vector
- Řetězce: použijtestd::string
- Volitelné objekty: použijtestd::optional
Nezpracovanénew/deletese mohou objevit v kódu knihovny (alokátory, vlastní kontejnery), ale kód aplikace by je nikdy neměl potřebovat. Pokud v recenzi kódu uvidítenew, zeptejte se proč – téměř vždy existuje bezpečnější alternativa.
- Neshoda
new/delete[]nebonew[]/deleteje nedefinované chování - Úniky paměti, double-free a use-after-free jsou tři smrtící chyby paměti
f(new A(), new B())může uniknout, pokud konstruktor vyvolá (před C++17 i s obálkami shared_ptr)- Umístěte nové konstrukce na danou adresu – zničte ručně, nikdy nepoužívejte
delete - Moderní kód aplikace C++ by měl mít nula nezpracovaných
new/delete– použijte chytré ukazatele a kontejnery
Kvíz — Otestujte své znalosti
(15 XP)1. Co se stane, když použijete`delete`v paměti přidělené pomocí`new[]`?
2. Proč může`f(new A(), new B())`unikat paměť?
3. Jak byste měli zničit objekt vytvořený s novým umístěním?