Přeskočit na obsah

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.

new_delete.cpp
#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í.

placement_new.cpp
#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).

Úskalí

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.