14

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?

underscore_d
  • 6,309
  • 3
  • 38
  • 64
ravin.wang
  • 1,122
  • 1
  • 9
  • 26

2 Answers2

12

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    MSVC seems to return a different pointer when creating arrays with placement new: https://stackoverflow.com/questions/15254/can-placement-new-for-arrays-be-used-in-a-portable-way/17011 – HolyBlackCat Jan 09 '18 at 08:21
  • 1
    @HolyBlackCat - Yeah, that's a dark corner I don't want to step into. If `T` turns out to be an array, I'm deleting this. – StoryTeller - Unslander Monica Jan 09 '18 at 08:23
  • 2
    I don't think you should delete this in any case, it's a nice answer. Also I found this: [expr.new] 1 *"If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression returns a pointer to the initial element of the array."* It seems to allow MSVC's weird treatement of arrays. – HolyBlackCat Jan 09 '18 at 08:27
  • @HolyBlackCat - I think the note in the first quoted paragraph explains why. It's to allow `char` arrays to be "aligned to any object", as is assumed for memory returned by `operator new`. – StoryTeller - Unslander Monica Jan 09 '18 at 08:29
  • 1
    Finally, [expr.eq]/2.2 says that, with an exception not relevant here, two pointers compare equal if they represent the same address (even if their values are different, as is the case here). – T.C. Jan 09 '18 at 08:43
8

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)...); 
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271