C++17 new / delete с выравниванием - как указать выравнивание в delete?
В С++17 появилась возможность добавлять выравнивание в new
. Однако код:
int main()
{
size_t n = 5;
double* Test = new(align_val_t(64)) double[n];
delete[] Test;
return 0;
};
Падает в исключение на операторе delete[]
где-то в недрах ntdll и отладчик сообщает о разрушении кучи.
Подскажите, как указать delete[]
что он имеет дело с выровненным объектом?
Дополнено:
int main()
{
struct alignas(64) a_double
{
double d;
};
size_t n = 5;
a_double* Test = new a_double[n];
std::cout << alignof(a_double) << std::endl;
delete[] Test;
return 0;
};
Вот такая конструкция на основе ms заметки заработала, но как то она выглядит стрёмно и монструозно - такой огород городить ради банального выравнивания.
Кроме того обратиться к Test[i]
как к double
уже не получится, нужно обращаться к полю Test[i].d
, что совсем уже плохо читаемо.
А ещё, как пояснил @user7860670, эта конструкция выравнивает не сам массив, а каждый элемент массива, что не соответствует моей задаче.
Ещё уточнил в коде, что меня интересует динамический массив.
Дополнение 2:
Запустил clang напрямую (до этого я запускал его из под msvc студии, а он там в режиме совместимости) и всё перестало падать и с просто delete[]
и с operator delete[](...)
.
Теперь гадаю - всё дело в каких то особенностях msvc, которые clang воспроизводит в режиме совместимости (но без сообщения об ошибке как в самом msvc) или сейчас не падает потому что "звёзды так сошлись", но потом может упасть.
Upd: Выяснил, что не может упасть, а точно падает, просто при запуске без отладчика этих исключений не видно.
Ответы (2 шт):
Синтаксис new(align_val_t(64)) double[5];
характеризуется дополнительной передачей placement
аргументов в круглых скобочках. Аналогичной формы для оператора delete
нет. При использовании placement
синтаксиса пользователь должен сам вызывать деструктор объектов и (при необходимости) подходящую функцию подчистки памяти.
#include <new>
#include <cstddef>
int main()
{
using item_t = double;
::std::align_val_t const align{64};
::std::size_t const count{5};
item_t * const p_items{new(align) item_t [count]{}};
for (auto p_item{p_items}; (p_items + count) != p_item; ++p_item)
{
p_item->~item_t();
}
::operator delete [](static_cast<void *>(p_items), align);
return 0;
}
Visual Studio такой сценарий не поддерживает, выдавая ошибку C2956, которая должна быть скорее предупреждением.
Здесь мне подсказали правильный ответ:
size_t n = 50;
auto Test = static_cast<double*>(::operator new[](n * sizeof(double), std::align_val_t(64)));
for (size_t i = 0; i < n; i++)
Test[i] = i; // проверка что память реально доступна
::operator delete[](Test, std::align_val_t(64));
Эта конструкция работает и в clang и в msvc без исключений, без неопределённого поведения и без ошибки C2956.
Единственное - не следует забывать что здесь конструкторы/деструкторы элементов массива не вызываются автоматически. Если они нужны, то их надо вызывать вручную. Но для нужных мне тривиальных типов (int, float, double и т.п.) они и не нужны.