2

I am not absolutely sure what is happening in the code below:

#include <iostream>

struct Foo
{
    double dummy{42};
    static void* operator new(std::size_t size, void* p)
    {
        std::cout << R"(Calling placement "operator new" for size )"
                  << size << '\n';
        return ::operator new(size, p);
    }

    Foo()
    {
        std::cout << "Foo::Foo()" << std::endl;
    }
    ~Foo()
    {
        std::cout << "~Foo::Foo()" << std::endl;
    }
};

int main()
{
    void* buf_heap = new char[128] {};
    Foo* pFoo_heap = new(buf_heap) Foo; // placement allocation

    // why does this line call the correct operator delete?
    delete pFoo_heap;

    // the line above seems to be equivalent to:
    // pFoo_heap->~Foo();
    // ::operator delete(buf_heap);
}

I know that whenever one uses placement new when constructing an object, the destructor should be manually called, followed by a call to release the memory (usually via ::operator delete or ::operator delete[], or free, depending on how placement new is implemented), see e.g. How to delete object constructed via placement new operator?

However, in my code above, I created a Foo object which I placed in heap-allocated memory. Then, when I call delete pFoo_heap, the destructor is automatically invoked (this I understand), however it seems also that the memory is also released (buf_heap in this case). That is, if I try after to do ::operator delete[](buf_heap); I'm getting a segfault.

So basically the line

delete pFoo_heap;

seems to be equivalent to

pFoo_heap->~Foo(); 
::operator delete[](buf_heap);

Is this indeed the case (or it is just UB)? Why is the memory allocated in buf_heap de-allocated? Or, in other words, does delete pFoo_heap; know where the memory came from, even if allocated via a placement new?

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 1
    Guess: you are lucky because your placed object and the heap block start at the same address. Try allocating `pFoo_heap` in the middle of `buf_heap` and see what happens. – Richard Critten Apr 17 '15 at 21:20
  • @RichardCritten great comment, I didn't think about this. So it is a nice example of UB... – vsoftco Apr 17 '15 at 21:21

2 Answers2

3

You asked:

// why does this line call the correct operator delete?
delete pFoo_heap;

// the line above seems to be equivalent to:
// pFoo_heap->~Foo();
// ::operator delete(buf_heap);

That is not true. Your code is subject to undefined behavior. From the C++ Standard:

5.3.5 Delete

2 If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section. In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined.

In your case, pFoo_heap was not created by a new-expression, buf_heap was created by a new-expression. You need to use:

pFoo_heap->~Foo();
delete [] buf_heap;

for a well behaved program.

Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • That is indeed the proper way to do things. – emvee Apr 17 '15 at 21:37
  • Thanks for the standard reference. I knew that indeed I should call first the dtor, then eventually release the memory, but wasn't sure why my snippet seem to work. But the line *"a pointer to a non-array object created by a previous new-expression"* seem to take in consideration also placement new, isn't it? As it is still a *new-expression*. – vsoftco Apr 17 '15 at 21:39
  • In 5.3.4, placement `new` seems to be a *new-expression*, so imo `pFoo_heap` was created by a *new-expression*. Am I misunderstanding something? – vsoftco Apr 17 '15 at 23:37
  • @vsoftco, The placement `new` in section 5.3.4 addresses all forms of placement `new` other than the one we are discussing here. The placement `new` that we are discussing here is more clearly mentioned in section 18.6.1.3. That section says *Intentionally performs no other action.* Also, there's a small section at http://en.cppreference.com/w/cpp/language/new that talks about the placement `new` we are discussing. – R Sahu Apr 22 '15 at 03:55
1

Why is the memory allocated in buf_heap de-allocated?

Because you're de-allocating it. Note that buf_heap == pFoo_heap, so when you do delete pFoo_heap that happens to work exactly as if you had done new Foo to begin with - the pointer you're deleting is pointing to memory that was previously allocated with new. So it... "works"...

Barry
  • 286,269
  • 29
  • 621
  • 977