mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-17 21:04:35 +03:00
80 lines
5.2 KiB
Markdown
80 lines
5.2 KiB
Markdown
# placement new для массивов
|
||
|
||
Вам посчастливилось добыть новую суперэффективную библиотеку для управления памятью?
|
||
Вы хотите пользоваться ею в C++ и не сталкиваться с надуманным UB из-за проблем с лайфтаймами?
|
||
|
||
Вам повезло! Просто выделяйте память своей библиотекой, создавайте
|
||
в выделенном буфере объекты с помощью placement new и забот не знайте!
|
||
|
||
```C++
|
||
void* buffer = my_external_malloc(sizeof(T), alignof(T));
|
||
auto pobj = new (buffer) T();
|
||
```
|
||
|
||
Красиво, просто, здорово!
|
||
|
||
А что если мы захотим выделить память и разместить в ней массив?
|
||
|
||
Нет ничего проще!
|
||
```C++
|
||
void* buffer = my_external_malloc(n * sizeof(T), alignof(T));
|
||
auto pobjarr = new (buffer) T[n];
|
||
```
|
||
Все, можно идти пить чай. Задача решена. Мы молодцы. Как похорошел C++ с 11-го стандарта!
|
||
|
||
Но не может же быть все так просто?
|
||
|
||
Конечно же нет! До C++20 вариант placement new для массивов имеет полное право испоганить вашу память.
|
||
|
||
Конструкция
|
||
```C++
|
||
new (buffer) T[n];
|
||
```
|
||
согласно примерам (§ 8.5.2.4 (15.4)) из стандарта [C++17](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf),
|
||
переводится в
|
||
```C++
|
||
operator new[](sizeof(T) * n + x, buffer);
|
||
// или operator new[](sizeof(T) * n + x, std::align_val_t(alignof(T)), buffer);
|
||
```
|
||
Где `x` — никак не специфицируемое неотрицательное число, предназначенное, например, чтобы застолбить место под какую-либо
|
||
метаинформацию о выделенном массиве: засунуть число элементов в начало области памяти или расставить маркеры начала/конца или еще что-нибудь, что обычно делают аллокаторы.
|
||
|
||
То есть placement new для массива вполне может полезть за пределы предоставленного вами буфера. Очень удобно!
|
||
|
||
В C++20 восхитительную [формулировку](https://eel.is/c++draft/expr.new#19.4) изменили.
|
||
|
||
Теперь же, если конструкция
|
||
```C++
|
||
new (arg1, arg2...) T[n];
|
||
```
|
||
соответствует вызову стандартного
|
||
```C++
|
||
void* operator new[]( std::size_t count, void* ptr);
|
||
```
|
||
То все будет хорошо. Никаких магических сдвигов на `+x` не возникнет.
|
||
|
||
Но если же какой-то доброжелатель определил свой собственный operator placement new... Впрочем, это уже совсем другая история...
|
||
|
||
--------------
|
||
|
||
Я не встречал ни одного компилятора, и ни одной поставки стандартной библиотеки, в которых стандартный placement new как-либо двигал указатель на пользовательский буфер.
|
||
Реальную угрозу трудноотлавливаемого UB в большей степени представляют user-defined версии placement new.
|
||
|
||
Чтобы обезопасить себя и вызвать настоящий стандартный placement new, нужно использовать
|
||
`::new` и кастить указатель на буфер к `void*`.
|
||
Либо положиться на алгоритмы `std::uninitialized_default_construct_n` и подобные ему.
|
||
|
||
|
||
Также нужно отметить, что в C++ нет placement delete синтаксиса.
|
||
Мы можем только явно вызвать `operator delete[](void* ptr, void* place)`, стандартная версия которого ничего не делает.
|
||
|
||
Тут, конечно, нужно понимать разницу между самим `operator delete` и синтаксическими конструкциями
|
||
`delete p` и `delete [] p`. Первый занимается только управлением памятью. Последние же — еще и вызывают деструкторы.
|
||
|
||
В C++ нет именно отдельной синтаксической конструкции, чтобы махом вызывать деструкторы элементов массива, созданного с помощью placement new. Это нужно делать вручную или использовать алгоритм `std::destroy`.
|
||
|
||
Ни в коем случае не стоит использовать `delete []` против указателя, полученного с помощью placement new [].
|
||
Будет [плохо](https://godbolt.org/z/WeWPseKoG).
|
||
|
||
|