14

(Note: this question was motivated by trying to come up with preprocessor hackery to generate a no-op allocation to answer this other question:

Macro that accept new object

...so bear that in mind!)

Here's a contrived class:

class foo {
private:
    int bar;
public:
    foo(int bar) : bar (bar)
        { std::cout << "construct foo #" << bar << std::endl; }
    ~foo()
        { std::cout << "destruct foo #" << bar << std::endl; }
};

...which I will allocate like this:

// Note: for alignment, don't use char* buffer with new char[sizeof(foo)] !
void* buffer = operator new(sizeof(foo));

foo* p1 = new (buffer) foo(1);
foo* p2 = new (buffer) foo(2);

/* p1->~foo(); */ /* not necessary per spec and problematic in gen. case */
p2->~foo();

On the gcc I've got around, I get the "expected" result:

construct foo #1
construct foo #2
destruct foo #2

Which is great, but could the compiler/runtime reject this as an abuse and still be on the right side of the spec?

How about with threading? If we don't actually care about the contents of this class (let's say it's just a dummy object anyway) will it at least not crash, such as in the even simpler application which motivated this with a POD int?

Community
  • 1
  • 1

3 Answers3

17

Peforming placement-new several times on the same block of memory is perfectly fine. Moreover, however strange it might sound, you are not even requred to destruct the object that already resides in that memory (if any). The standard explicitly allows that in 3.8/4

4 A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released;[...]

In other words, it is your responsibility to take into account the consequences of not calling the destructor for some object.

However, calling the destructor on the same object twice as you do in your code is not allowed. Once you created the second object in the same region of memory, you effectively ended the lifetime of the first object (even though you never called its destructor). Now you only need to destruct the second object.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Cool. I took the first destructor out to focus the question on what I was more interested in. Thanks! – HostileFork says dont trust SE Sep 23 '11 at 04:59
  • Wow! The vagueness of the last bit of the last line of your quote scares me a little - "any program that depends on the side effects produced by the destructor has undefined behavior". In particular what does "depends on" mean in this context? – Michael Anderson Sep 23 '11 at 05:01
  • @Michael Anderson: I already removed it from the quote as irrelevant to the question (or so it seemed to me). But I'm not exactly sure what it means either. – AnT stands with Russia Sep 23 '11 at 05:05
  • Its kind of relevant to this question though. Does performing output count as depending on side effects produced by the destructor?.. The Language Lawyer in me says yes - and hence the example given is undefined behaviour. Unless there is a more sensible interpretation of that part of the quote.. (Though IMO the whole section would make more sense without that ugly extra bit) – Michael Anderson Sep 23 '11 at 05:11
  • @Michael: I can imagine that if we were talking of a RAII object (shared pointer/handle, ...) you'd be very annoyed at not having the destructor called. – Matthieu M. Sep 23 '11 at 06:13
  • @Matthieu M. Annoyed yes, but IMO thats like allocating a RAII object via new and never calling delete. Might make me sad that the destructor is not called, but it shouldn't be "undefined behavior". – Michael Anderson Sep 23 '11 at 07:50
  • @Michael: it is not, you're just leaking some resource :) – Matthieu M. Sep 23 '11 at 08:22
  • @Matthieu M. I agree that is what _should_ happen. ... However the quote above continues: "if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.". (my quote may be from a slightly different version of the std draft). That seems to say if your destructor is supposed to do something and you are ment to manually call it, but you never do, then you get undefined behavior. – Michael Anderson Sep 23 '11 at 09:19
  • 1
    @MichaelAnderson **It means nothing.** They could as well have said "but be careful". This is the kind of stuff you see in a pretty baldy written specification, like C++. Don't loose sleep on it! – curiousguy Oct 12 '11 at 04:25
  • 1
    @curiousguy: Of course, whether such language should mean anything doesn't necessarily affect whether compiler writers will use it as an excuse to behave in arbitrary fashion. – supercat Feb 04 '17 at 05:52
3
foo* p1 = new (buffer) foo(1);
foo* p2 = new (buffer) foo(2);
p1->~foo();
p2->~foo();

You are destructing the same object twice, and that alone is undefined behavior. Your implementation may decide to order a pizza when you do that, and it would still be on the right side of the spec.

Then there is the fact that your buffer may not be properly aligned to emplace an object of type foo, which is again non standard C++ (according to C++03, I think C++11 relaxes this).

Update: Regarding the question specified in the title,

Is it well-defined/legal to placement-new multiple times at the same address?

Yes, is it well-defined to placement-new multiple times at the same address, provided that it points to raw memory.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • How come you always mention ordering pizza? :) You seem to make a lot of people hungry. – Mateen Ulhaq Sep 23 '11 at 04:39
  • The question is still relevant even without the destructor calls, as there's no technical requirement to call them. I'm apparently looking for the `language-lawyer` proof-from-spec of the related points. :) – HostileFork says dont trust SE Sep 23 '11 at 04:41
  • Ordering pizza would be a far more productive output than what it usually does in that case. I want to see the signal handlers and/or OS support needed since that would be a great feature to add to this Fuel System Icing Inhibitor we're developing. We've managed to shoehorn in most of a disk operating system so far.... – Mike DeSimone Sep 23 '11 at 04:42
  • Seriously, though, you can solve the alignment problem by properly declaring `buffer` as an array of a fundamental type that needs the same alignment. E.g. if you need four-byte alignment, declare it as `int32_t buffer[sizeof(foo) / 4 + 1];` or similar. And get rid of the `&` in front of `buffer` so the compiler doesn't bark. – Mike DeSimone Sep 23 '11 at 04:45
  • @Hostile Fork: Can't you just `#undef` that macro and provide your own definition replacement for it? – K-ballo Sep 23 '11 at 04:45
  • And if you decide to go with this code, then at least make your buffer static so you don't even get a chance to leak that memory. – K-ballo Sep 23 '11 at 04:46
  • @K-ballo You can add that to the suggestions on that question (which I did not ask), no one thus far mentioned `#undef`! But this is different. – HostileFork says dont trust SE Sep 23 '11 at 04:47
  • @GMan: I think that's the case with C++11, but in C++03 the situation is different. – K-ballo Sep 23 '11 at 04:57
0

No - this doesn't look right.

When you use placement new, the object will be constructed at the address you pass. In this example you're passing the same address (i.e. &buffer[0]) twice, so the second object is just obliterating the first object that's already been constructed at this location.

EDIT: I don't think I understand what you're trying to do.

If you have a general object type (that may have non-trivial ctor/dtor's that might allocate/deallocate resources) and you obliterate the first object by placement new'ing over the top of it without first explicitly calling it's destructor, this will at least be a memory leak.

Darren Engwirda
  • 6,915
  • 4
  • 26
  • 42