11

Ignoring usefulness of such practice. (Though real-life examples are welcome, of course.)

For example, the following program outputs the correct value for a:

#include <iostream>

using namespace std;

int main()
{
  int a = 11111;
  int i = 30;

  int* pi = new (&i) int();

  cout << a << " " << endl;
}

But isn't new-allocation supposed to create some bookkeeping information adjacent to i (for correct subsequent deallocation), which in this case is supposed to corrupt the stack around i?

Leo Heinsaar
  • 3,887
  • 3
  • 15
  • 35

4 Answers4

8

Yes, it's perfectly OK to perform placement-new with a pointer to an object on the stack. It will just use that specific pointer to construct the object in. Placement-new isn't actually allocating any memory - you have already provided that part. It only does construction. The subsequent deletion won't actually be delete - there is no placement delete - since all you need to do is call the object's destructor. The actual memory is managed by something else - in this case your stack object.

For example, given this simple type:

struct A {
    A(int i) 
    : i(i)
    {
        std::cout << "make an A\n";
    }

    ~A() {
        std::cout << "delete an A\n";
    }

    int i;
};

The following is completely reasonable, well-behaved code:

char buf[] = {'x', 'x', 'x', 'x', 0};
std::cout << buf << std::endl;  // xxxx
auto a = new (buf) A{'a'};      // make an A
std::cout << a->i << std::endl; // 97
a->~A();                        // delete an A

The only case where this would be invalid would be if your placement-new-ed object outlasts the memory you new-ed it on - for the same reason that returning a dangling pointer is always bad:

A* getAnA(int i) {
    char buf[4];
    return new (buf) A(5); // oops
}
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • When I try your example [code in gcc, it prints 'a'](http://coliru.stacked-crooked.com/a/084936295ee3c0ec) on the last line, while [clang prints 'b'](http://coliru.stacked-crooked.com/view?id=b1dd7df75d552a9d), just as you show. Is this a gcc bug or is there undefined behavior lurking somewhere in the code? – Davit Tevanian Nov 04 '16 at 12:45
  • Shouldn't `buf` by aligned by `A`, as in `alignas(A) char buff[]...`? Also, any idea why Davit's example produces different results on GCC while optimizing with `-Os`, even as of today? – 303 Jan 26 '22 at 00:23
  • @303 dead store elimination – Barry Jan 26 '22 at 01:56
  • Ah, I see now. The lifetime of `buf` has ended after the use of placement new, so the optimizer is free to assume that `buf` will no longer be accessed, the same goes for accessing `a` after calling its destructor, right? – 303 Jan 26 '22 at 10:29
2

Placement new constructs the element in place and does not allocate memory.

The "bookkeeping information" in this case is the returned pointer which ought to be used to destroy the placed object.

There is no delete associated with the placement since placement is a construction. Thus, the required "clean up" operation for placement new is destruction.

The "usual steps" are

  1. 'Allocate' memory
  2. Construct element(s) in place
  3. Do stuff
  4. Destroy element(s) (reverse of 2)
  5. 'Deallocate' memory (reverse of 1)

(Where memory can be stack memory which is neither required to be explicitly allocated nor deallocated but comes and goes with a stack array or object.)

Note: If "placing" an object into memory of the same type on the stack one should keep in mind that there's automatic destruction at the end of the object's lifetime.

{ 
  X a;
  a.~X(); // destroy a
  X * b = new (&a) X(); // Place new X
  b->~X(); // destroy b
} // double destruction
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
1

No, because you don't delete an object which has been placement-newed, you call its destructor manually.

struct A {
    A() { std::cout << "A()\n"; }
    ~A() { std::cout << "~A()\n"; }
};

int main()
{
    alignas(A) char storage[sizeof(A)];

    A *a = new (storage) A;
    std::cout << "hi!\n";

    a->~A();
    std::cout << "bye!\n";
}

Output:

A()
hi!
~A()
bye!

In your case, there's no need to call a destructor either, because int is trivially-destructible, meaning that its destructor would be a no-op anyway.

Beware though not to invoke placement-new on an object that is still alive, because not only will you corrupt its state, but its destructor will also be invoked twice (once when you call it manually, but also when the original object should have been deleted, for exampel at the end of its scope).

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 1
    @LightnessRacesinOrbit what do you mean ? – Quentin Jul 15 '15 at 15:11
  • 1
    Exactly what I said. Its destructor may be a no-op but it's invoked twice if you call a destructor _and_ let it fall out of scope (which you can't avoid). Traditionally, placement new happens into a `char[]` (as you do in your answer), not into an `int` (as the OP does in the question). – Lightness Races in Orbit Jul 15 '15 at 15:26
  • 1
    @LightnessRacesinOrbit oh, I was searching in my example. Note added, thank you ! – Quentin Jul 15 '15 at 16:15
0

Placement new does construction, not allocation, so there's no bookkeeping information to be afraid of.

I can at the moment think of one possible use case, though it (in this form) would be a bad example of encapsulation:

#include <iostream>
using namespace std;
struct Thing {
 Thing (int value) {
  cout << "such an awesome " << value << endl;
 }
};
union Union {
 Union (){}
 Thing thing;
};
int main (int, char **) {
 Union u;
 bool yes;
 cin >> yes;
 if (yes) {
  new (&(u.thing)) Thing(42);
 }
 return 0;
}

Live here

Though even when the placement new is hidden in some member function, the construction still happens on the stack.

So: I didn't look in the standard, but can't think of why placement new on the stack shouldn't be permitted.

A real world example should be somewhere in the source of https://github.com/beark/ftl ... in their recursive union, which is used for the sum type.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63