Страницы

Поиск по вопросам

понедельник, 26 ноября 2018 г.

Как использовать динамическую память в c++?

Как работает динамическая память и какими операторами пользоваться для работы с ней в C++?


Ответ

В С++ объекты создаются динамически при помощи new-выражений и разрушаются при помощи delete-выражений.
T* obj = new T; delete obj;
T* arr = new T[100]; delete[] arr;
New-выражения и delete-выражения используют функции выделения динамической памяти operator new, operator new[] и функции освобождения operator delete, operator delete[] соответственно.
Реализация С++ предоставляет несколько глобальных функций выделения и освобождения. При этом они могут быть переопределены пользователем без нарушения ODR (но не более одного раза в программе).
void* operator new(std::size_t); void operator delete(void*) noexcept; void operator delete(void*, std::size_t) noexcept;
void* operator new[](std::size_t); ...
Пользователь также может добавлять свои функции выделения и освобождения, в виде статических членов класса или свободных функций в глобальном пространстве имен. Такие пользовательские функции должны соблюдать семантику встроенных (не выделять уже выделенные адреса и т.п.).
Выражения new и delete для одного объекта
Функции operator new и operator delete только выделяют и освобождают память. Конструирование и разрушение объекта происходит в самом выражении new и delete. Для вызова конструктора используется new который не выделяет память - размещающий new
// Эквивалент кода // T* obj = new T(a1, a2); T* obj; { void* mem = operator new(sizeof(T)); try { obj = new(mem) T(a1, a2); // Размещающий new, вызывает конструктор T на памяти mem } catch (...) { // Исключение в конструкторе operator delete(mem); throw; } }
// Примерный* эквивалент кода // delete obj; obj->~T(); // Вызов деструктора operator delete(obj);
// *) При виртуальном деструкторе компилятор сгенерирует что-то похожее на void* mem = obj->~T(); // псевдокод operator delete(mem); // Т.к. при множественном наследовании может быть mem != obj
Из кода выше видно, что new-выражение требует обе функции, operator new и operator delete
Функции operator new и operator delete в стандартной библиотеке
Обычный new
void* operator new(std::size_t size);
Использование:
T* p = new T; assert(p != nullptr);
Примерная реализация:
void* operator new(std::size_t size) { for (;;) { void* mem = unspecified_alloc(size); // Может быть malloc, зависит от реализации. if (mem) return mem;
// Пользовательская функция-обработчик нехватки памяти. std::new_handler user_handler_fn = std::get_new_handler(); if (!user_handler_fn) throw std::bad_alloc(); user_handler_fn(); // Либо предоставляет память, // либо бросает bad_alloc или завершает программу. } }
Пользователь может установить свою функцию new_handler при помощи функции std::set_new_handler
Не-бросающий исключений new
void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
Использование:
T* p = new(std::nothrow) T; if (!p) { /* не удалось выделить память */ }
Примерная реализация:
void* operator new(std::size_t size, const std::nothrow_t&) noexcept { try { return operator new(size); } catch (const std::bad_alloc&) { return nullptr; } }
Размещающий new
void* operator new(std::size_t size, void* ptr) noexcept { return ptr; }
Ничего не выделяет, возвращают аргумент ptr Позволяют вызвать конструктор объекта.
char mem[sizeof(T)]; T* p = new(mem) T;
Размещающий operator delete также существует, и также ничего не делает. Нужен только потому что new-выражение требудет наличия operator delete
Динамические массивы, new[] и delete[]
Функции operator new[] и operator delete[] служат для выделения памяти, и ничем не отличаются от функций для одного объекта. Стандартные реализации просто вызывают operator new
void* operator new[](std::size_t size) { return operator new(size); }
Размер динамического массива сохраняется самим выражением new[], примерно так:
// Эквивалент кода (без учета выравнивания) // T* arr = new T[100]; T* arr; { void* mem = operator new[](sizeof(std::size_t) + sizeof(T)); std::size_t& size = *reinterpret_cast(mem); arr = reinterpret_cast(reinterpret_cast(mem) + sizeof(std::size_t)); try { for (size = 0; size != 100; ++size) { new(tmp_arr + size) T; // Конструктор элемента } } catch (...) { while (size != 0) { --size; arr[size].~T(); // Деструктор элемента } operator delete[](mem); throw; } }
// Эквивалент кода (без учета выравнивания) // delete[] arr; { std::size_t& size = reinterpret_cast(arr)[-1]; while (size != 0) { --size; arr[size].~T(); // Деструктор элемента } operator delete[](size); }
Массивы созданные при помощи выражения new T[N] могут быть освобождены только через delete[] и наоборот, объекты созданные new T(), должны быть быть освобождены delete
Размещающий new[]
void* operator new[](std::size_t size, void* ptr) noexcept;
Он существует, но мы не знаем сколько байт будут резервироваться копилятором под размер объекта, поэтому не сдедует его использовать:
// char mem[100 * sizeof(T) + sizeof(std::size_t)]; // Компилятор может использовать больше чем sizeof(std::size_t), тогда мы выйдем за границы mem. // new(mem) T[100];

Комментариев нет:

Отправить комментарий