После размещения-new в буфер, буфф и экземпляр имеют одинаковый адрес void *?

См. ниже код:

unsigned char* p = new unsigned char[x];
CLASS* t = new (p) CLASS;
assert((void*)t == (void*)p);

Можно ли считать (void*)t == (void*)p?

Ответы

Ответ 1

Да, вы можете. Я считаю, что это гарантировано несколькими положениями.

  • [expr.new]/10 - Акцент на мой

    Новое выражение передает объем пространства, запрошенный для функция распределения как первый аргумент типа std:: size_t. Это аргумент должен быть не меньше размера создаваемого объекта; он может быть больше размера создаваемого объекта, только если объект представляет собой массив. Для массивов char и unsigned char, разница между результатом нового выражения и адресом возвращаемый функцией распределения, должен быть целым кратным строжайшее требование основного выравнивания ([basic.align]) любого тип объекта, размер которого не превышает размер массива, создано. [Примечание: поскольку предполагается, что функции распределения возвращаются указатели на хранилище, которые соответствующим образом выровнены для объектов любого тип с фундаментальным выравниванием, это ограничение на распределение массива служебные данные позволяют распространять идиому выделения массивов символов в какие объекты других типов будут позже размещены. - конечная нота]

    Что мне читает, как новое выражение должно создать объект (предполагая его не типа массива) по точному адресу, возвращаемому функцией распределения. Поскольку вы используете встроенное размещение new, это приведет нас к следующему

  • [new.delete.placement]

    Эти функции зарезервированы, программа на С++ может не определять функции которые заменяют версии в стандартной библиотеке С++ ([ограничения]). Положения ([basic.stc.dynamic]) не применяются к этим зарезервированным формам размещения оператора new и оператора delete.

    void* operator new(std::size_t size, void* ptr) noexcept;
    

    Возвращает: ptr.

    Примечания: Преднамеренно не выполняет никаких других действий.

    Что гарантирует адрес, который вы передаете в выражение, является точным адресом выделенного вами массива символов. Это потому, что преобразования в void* не меняют адрес источника.

Я думаю, это достаточно, чтобы обещать, что адреса одинаковы, даже если указатели не являются взаимозаменяемыми в целом. Итак, согласно [expr.eq]/1 (спасибо @T.C.):

Два указателя одного типа сравнивают одинаковые, если и только если они оба нулевые, оба указывают на одну и ту же функцию, или оба представляют одинаковые адрес ([basic.compound]).

Сравнение должно иметь значение true, опять же, потому что адреса одинаковы.

Ответ 2

Можно ли считать (void*)t == (void*)p?

Не обязательно.

Если автор класса перегружает CLASS::operator new(size_t, unsigned char*), например, этот оператор может возвращать что-либо, кроме второго аргумента, например:

struct CLASS
{
    static void* operator new(size_t, unsigned char* p) { return p + 1; }
};

Если вы хотите, чтобы это новое выражение вызывало стандартный оператор размещения без размещения места размещения, код должен

  • Включить заголовок <new>.
  • Обязательно передайте ему аргумент void*.
  • Префикс он с оператором разрешения области :: для обхода CLASS::operator new, если таковой имеется.

например:.

#include <new> 
#include <cassert> 

unsigned char p[sizeof(CLASS)];
CLASS* t = ::new (static_cast<void*>(p)) CLASS;
assert(t == static_cast<void*>(p));

В этом случае t == static_cast<void*>(p) действительно.

Это, на самом деле, то, что делает стандартная библиотека GNU С++:

template<typename _T1, typename... _Args>
inline void _Construct(_T1* __p, _Args&&... __args) { 
    ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); 
}