Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

After placement-new into buffer, do buffer and instance have same void* address?

Please see the below code:

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

Can I assume (void*)t == (void*)p?

like image 638
ravin.wang Avatar asked Jan 09 '18 07:01

ravin.wang


People also ask

What does the placement new do?

Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.

Does placement New allocate memory?

Because placement new does not allocate memory, you should not use delete to deallocate objects created with the placement syntax. You can only delete the entire memory pool ( delete whole ). In the example, you can keep the memory buffer but destroy the object stored in it by explicitly calling a destructor.

Does vector use placement new?

std::vector<T,Allocator>::emplace_back Appends a new element to the end of the container. The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.


2 Answers

Yes you may. I believe it's guaranteed by several provisions.

  1. [expr.new]/10 - Emphasis mine

    A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the strictest fundamental alignment requirement ([basic.align]) of any object type whose size is no greater than the size of the array being created. [ Note: Because allocation functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating character arrays into which objects of other types will later be placed. — end note ]

    Which to me reads like the new expression must create an object (assuming it's not of array type) at the exact address returned by the allocation function. Since you are using the built-in placement new, this take us to the following

  2. [new.delete.placement]

    These functions are reserved, a C++ program may not define functions that displace the versions in the Standard C++ library ([constraints]). The provisions of ([basic.stc.dynamic]) do not apply to these reserved placement forms of operator new and operator delete.

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

    Returns: ptr.

    Remarks: Intentionally performs no other action.

    Which guarantees the address you pass to the expression is the exact address of the character array object you allocated. That's because conversions to void* do not change the source address.

I think it's enough to promise the addresses are the same, even if the pointers are not interchangeable in general. So according to [expr.eq]/1 (thanks to @T.C.):

Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address ([basic.compound]).

The comparison must yield true, again because the addresses are the same.

like image 82
StoryTeller - Unslander Monica Avatar answered Oct 12 '22 00:10

StoryTeller - Unslander Monica


Can I assume (void*)t == (void*)p?

Not necessarily.

If the author of the class overloads CLASS::operator new(size_t, unsigned char*), for example, that operator can return anything other than the second argument, e.g.:

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

If you would like this new expression to call the standard non-allocating placement new operator the code needs to

  1. Include header <new>.
  2. Make sure to pass it a void* argument.
  3. Prefix it with scope resolution operator :: to bypass CLASS::operator new, if any.

E.g.:

#include <new> 
#include <cassert> 

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

In this case t == static_cast<void*>(p) indeed.

This is, in fact, what GNU C++ standard library does:

template<typename _T1, typename... _Args>
inline void _Construct(_T1* __p, _Args&&... __args) { 
    ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); 
}
like image 35
Maxim Egorushkin Avatar answered Oct 12 '22 00:10

Maxim Egorushkin