Подтвердить что ты не робот

Размещение новых и выравнивание в С++

Рассмотрим следующий фрагмент кода, создающий экземпляр структуры 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 здесь. Я знаю, что все может получиться, если будет задействовано множественное наследование и прочее.

4b9b3361

Ответ 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 Примечания: Преднамеренно не выполняет никаких других действий.

И это гарантирует, что функция распределения возвращает переданный указатель без изменений.