15

I am learning about placement-new in C++ using the books listed here. Now, to look at some examples, I came across the following snippet in one of the SO post that claims that it (the given example) has undefined behavior :

For example, this has UB:

void ub() {
   alignas(string) char buf[sizeof(string)]; // memory is allocated
   new(buf) string("1");                     // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory! 

As you can see the user claims that the above snippet has undefined behaviour. But I think that it has memory leak and not UB. Can someone tell me whether the above snippet has UB or memory leak or both and if my understanding (that it has memory leak but not UB) is correct or not.

Kal
  • 475
  • 1
  • 16
  • See https://stackoverflow.com/questions/41385355/is-it-ok-not-to-call-the-destructor-on-placement-new-allocated-objects – interjay Jul 03 '22 at 12:47
  • 3
    The example is complicated by many implementations using the [short-string-optimization](https://stackoverflow.com/questions/10315041/meaning-of-acronym-sso-in-the-context-of-stdstring), to avoid allocating heap memory for the first 15-20 characters in a string. So perhaps no leak either. – BoP Jul 03 '22 at 13:20
  • I guess it's UB. Not for `std::string` perhaps, but certainly for some custom type that listens on a timer or something. – Paul Sanders Jul 03 '22 at 13:20

4 Answers4

13

There is a sentence in the standard which is not very clear about its meaning in [basic.life]/5 saying that if a destructor call is omitted like in your quoted example, then

any program that depends on the side effects produced by the destructor has undefined behavior.

It is not clear what "depends on the side effects" here is supposed to mean. If you consider the side effect of leaking memory something that your program "depends" on, then maybe it applies, but I doubt that this is the intended reading.

There is CWG issue 2523 suggesting to remove this phrase and replace with just a non-normative note mentioning the potential problems of not calling the destructor in such a situation. See also discussion here.

Aside from that, there is no undefined behavior, just a memory leak. Of course with other types than string it could easily be possible to cause undefined behavior if you don't properly call destructors before deallocating their memory.

In practice you should never let this situation happen, even if only to avoid the memory leak. So you can practically treat it almost like undefined behavior.

user17732522
  • 53,019
  • 2
  • 56
  • 105
7

Nothing really wrong with your code in this case other than the obvious leaking of memory.

The constructor for the type you construct could have side effects though, like adding the constructed object to a global list of all such objects. The destructor would then remove the object from said list but you never call the destructor. So the global list ends up with a dangling pointer.

Note: Modern C++ has construct_at to replace your placement new.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
0

One could argue either way. Most of the time it would not matter at all, though obviously the better (safer, memory & sanity-preserving) way would be to call the destructor.

Let us imagine you have class Foo with member int foosOutlived and the following:

static std::list<Foo*> ALL_MY_FOO;

Foo::Foo()
 : foosOutlived(0)
{
    ALL_MY_FOO.push_back(this);
}

Foo::~Foo()
{
    ALL_MY_FOO.remove(this);
    for (Foo* aFoo : ALL_MY_FOO)
        aFoo->foosOutlived += 1;
}  

If the memory is deallocated and you fail to call the destructor here, you would retain a dangling pointer in ALL_MY_FOO...

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling a destructor or pseudo-destructor for the object.
For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

The salient point appears to be: Does the programm depend on the side effects produced by the destructor? Then not calling the destructor is UB.

CharonX
  • 2,130
  • 11
  • 33
0

One of the (IMHO misguded) design philosophies behind the C and C++ Standards was that optimizations should never observably affect any defined program behavior. As a consequence, the Standards have generally sought to explicitly classify as UB any action whose behavior might observably be affected by optimization, though fortunately in some specific cases the authors of the Standard have opted to more usefully recognize ways in which allowable optimizations might affect the behavior of defined programs [e.g. allowing consolidation of allocation requests]. The quoted sentence, which predates that trend, exemplifies this perfectly.

Many common and useful optimizations involve the reordering of unrelated actions within a program, such as moving redundant operations within a loop so that they are performed just once before the loop starts. If the program were to exit unexpectedly between the time a hoisted operation on an object was performed and the time the operation would have been performed in the code as written, and if this happened to cause the object's destructor to execute in a way that revealed the object's state, this could yield behavior that could never have occurred in the absence of optimization. In order to ensure that no defined programs can have their behavior affected by optimization, it is thus necessary for the Standard to classify the aformentioned situation as UB.

supercat
  • 77,689
  • 9
  • 166
  • 211