Размещение новых и выравнивание в С++
Рассмотрим следующий фрагмент кода, создающий экземпляр структуры POD (простые старые данные):
#include <new>
#include <cassert>
#include <cstddef>
struct Test
{
int a;
char b;
double c;
};
int main()
{
const std::size_t minimumNumberOfBytes = sizeof( Test ) * 4;
// Get a block of memory that can accommodate a Test instance and then some!
void* const ptrToMemBlock = new char[ minimumNumberOfBytes ];
assert( ptrToMemBlock );
// Construct a Test instance in-place.
const Test* const testInstance( ::new ( ptrToMemBlock ) Test() );
// Is this assumption guaranteed to be true?
assert( testInstance == ptrToMemBlock );
}
Является ли предположение, представленное окончательным утверждением() гарантированным, всегда правильным? Или можно предположить, что компилятор может решить создать экземпляр Test, скажем, несколько байтов после начала блока памяти, который я указал в новом вызове размещения?
Обратите внимание, что я спрашиваю конкретно о типах POD здесь. Я знаю, что все может получиться, если будет задействовано множественное наследование и прочее.
Ответы
Ответ 1
Да, это утверждение будет выполнено. Любое выражение new
, создающее единый объект, должно запрашивать точно sizeof(Test)
байты хранения из функции распределения; и поэтому он должен поместить объект в начале этого хранилища, чтобы иметь достаточно места.
Примечание. Это основано на спецификации нового выражения в С++ 11. Похоже, что С++ 14 изменит формулировку, поэтому ответ может быть другим в будущем.
Ответ 2
Это утверждение всегда будет выполняться, потому что new
требуется для возврата блоков памяти с МАКСИМАЛЬНЫМ возможным выравниванием. BTW - ваш первый assert()
бесполезен, так как нормальный new
не возвращает nullptr
- он отбрасывает или прерывает, только "nothrow new
" может возвращать nullptr
.
Ответ 3
Да, последний assert
гарантированно удерживается, потому что эта форма размещения-new всегда должна возвращать пройденный указатель, не используя для себя какое-либо пространство:
5.3.4 Новый [expr.new]
8 Новое выражение может получить хранилище для объекта, вызвав функцию распределения (3.7.4.1). [...]
10 Реализация позволяет опустить вызов сменной глобальной функции распределения (18.6.1.1, 18.6.1.2). Когда это делается, хранилище вместо этого обеспечивается реализацией или обеспечивается расширением выделения другого нового выражения. [...]
11 Когда новое выражение вызывает функцию распределения и , что выделение не было расширено, новое выражение передает объем пространства, запрошенный функции распределения, в качестве первого аргумента типа std::size_t
. Этот аргумент должен быть не меньше размера создаваемого объекта; он может быть больше размера создаваемого объекта, только если объект является массивом.
[...]
Ваше новое выражение вызывает глобальную функцию размещения - новое распределение.
Это неприменимая функция, поэтому распределение не может быть расширено или опущено.
Кроме того, вы не выделяете массив, а только один объект, поэтому никакое заполнение запроса не может возникать вообще.
18.6.1.3 Формы размещения [new.delete.placement]
1 Эти функции зарезервированы, программа на С++ не может определять функции, которые вытесняют версии в стандартной библиотеке С++ (17.6.4). Положения (3.7.4) не применяются к этим зарезервированным формам размещения operator new
и operator delete
.
void* operator new(std::size_t size, void* ptr) noexcept;
2 Возвращает: ptr
.
3 Примечания: Преднамеренно не выполняет никаких других действий.
И это гарантирует, что функция распределения возвращает переданный указатель без изменений.