Invoking placement delete when object construction throws

152 views
Skip to first unread message

Belloc

unread,
Dec 1, 2015, 1:08:43 PM12/1/15
to ISO C++ Standard - Discussion
Consider the following snippet:

#include <iostream>

void* operator new  (std::size_t count, int i1, int i2, int i3){
   
void *p = malloc(count);
   
if (!p) throw std::bad_alloc{};
    std
::cout << "operator new" << '\n';
   
return p;
}

void operator delete (void* p, int j1, int j2, int j3)
{
    free
(p);
    std
::cout << "operator delete" << '\n';
    std
::cout << j1 << ' ' << j2 << ' ' << j3 << '\n';
}

class T{};

class A {
public:
    A
() { std::cout << "A()" << '\n'; throw T{}; };
   
~A() { std::cout << "~A()" << '\n'; }
};

int main()
{
   
try
   
{
        A
*p = new(1, 2, 3) A;
       
delete p;
   
}
   
catch (std::bad_alloc&) { exit(1); }
   
catch ( T& ) {  }
}

The code prints the following results correctly:

operator new
A
()
operator delete
1 2 3


However, if I comment out the catch(T&){ } above, the code aborts without invoking the operator delete(void*, int, int, int). 
What I'm trying to understand is the logic used by the compiler to determine when the placement delete is invoked, or not. With that in mind, I posted below my idea of a pseudo-code for the new-expression new(1, 2, 3) A used above inside main().

int main()
{    
   
try
   
{
        A
* p = operator new(sizeof(A), 1, 2, 3);
       
try
       
{
            p
->A();        
       
}
       
catch ( T& )
       
{
           
operator delete(p, 1, 2, 3);
       
}
       
delete p;  // this will invoke the usual operator delete(void*)
   
}
   
catch (std::bad_alloc&) { exit(1); }
   
catch ( T& ) {  }
}

If the pseudo-code is correct, the operator delete(void*, int, int, int) should be invoked even when catch(T&){ } is commented out in the code.

What am I missing here?

Belloc

unread,
Dec 1, 2015, 1:16:07 PM12/1/15
to ISO C++ Standard - Discussion
In the pseudo-code, I forgot to include a throw; after he operator delete call.

Richard Smith

unread,
Dec 1, 2015, 2:56:31 PM12/1/15
to std-dis...@isocpp.org
[except.handle]p9: "If no matching handler is found, the function std::terminate() is called; whether or not the stack is
unwound before this call to std::terminate() is implementation-defined (15.5.1)."

[expr.new]p20: "If any part of the object initialization described above terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression."

Note that when the initialization causes an exception to be thrown, one of two things happens:
1) The stack is unwound. In this case, the initialization terminates by throwing an exception as the stack is unwound out of the constructor call, and the deallocation function will then be called.
2) The stack is not unwound and std::terminate is called. In this case, the initialization does not ever terminate (because std::terminate does not return), so the deallocation function is not called.

So, for an implementation that does not do stack unwinding when there is no matching handler for a thrown exception, your 'operator delete' will not be invoked (and nor will any destructors for local variables).

Belloc

unread,
Dec 1, 2015, 3:26:46 PM12/1/15
to ISO C++ Standard - Discussion


On Tuesday, December 1, 2015 at 5:56:31 PM UTC-2, Richard Smith wrote:
[except.handle]p9: "If no matching handler is found, the function std::terminate() is called; whether or not the stack is
unwound before this call to std::terminate() is implementation-defined (15.5.1)."

[expr.new]p20: "If any part of the object initialization described above terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression."

Note that when the initialization causes an exception to be thrown, one of two things happens:
1) The stack is unwound. In this case, the initialization terminates by throwing an exception as the stack is unwound out of the constructor call, and the deallocation function will then be called.
2) The stack is not unwound and std::terminate is called. In this case, the initialization does not ever terminate (because std::terminate does not return), so the deallocation function is not called.

So, for an implementation that does not do stack unwinding when there is no matching handler for a thrown exception, your 'operator delete' will not be invoked (and nor will any destructors for local variables).

Thanks for your reply.

I can't see how a matching handler is not found. If you look at my pseudo-code, a matching handle will always be found when the constructor fails, even when the second catch is commented out. That's the point that I don't understand.

Greg Marr

unread,
Dec 1, 2015, 3:45:13 PM12/1/15
to ISO C++ Standard - Discussion
Where is the handler for the T that is thrown in the A constructor?  Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.

Belloc

unread,
Dec 1, 2015, 3:55:10 PM12/1/15
to ISO C++ Standard - Discussion


On Tuesday, December 1, 2015 at 6:45:13 PM UTC-2, Greg Marr wrote:

Where is the handler for the T that is thrown in the A constructor?  Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.

Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  

int main()
{    
   
try
   
{
        A
* p = operator new(sizeof(A), 1, 2, 3);
       
try
       
{
            p
->A();        
       
}

       
catch ( T& )            // the throw is caught here

Richard Smith

unread,
Dec 1, 2015, 5:21:41 PM12/1/15
to std-dis...@isocpp.org
On Tue, Dec 1, 2015 at 12:55 PM, Belloc <jabe...@gmail.com> wrote:


On Tuesday, December 1, 2015 at 6:45:13 PM UTC-2, Greg Marr wrote:

Where is the handler for the T that is thrown in the A constructor?  Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.

Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  

int main()
{    
   
try
   
{
        A
* p = operator new(sizeof(A), 1, 2, 3);
       
try
       
{
            p
->A();        
       
}

       
catch ( T& )            // the throw is caught here

No, this was not in your original program, and is not implied by the semantics of the new-expression. Here's a more accurate lowering of your original code:

int main() {    
  try {
    A *p;
    {
      void *q = operator new(sizeof(A), 1, 2, 3);
      struct cleanup {
        void *p; int a; int b; int c;
        ~cleanup() { operator delete(p, 1, 2, 3); }
      } __cleanup = {q, 1, 2, 3};
      p = new (q) A; // call constructor of A
    }

    delete p;  // this will invoke the usual operator delete(void*)
  }
  catch (std::bad_alloc &) { exit(1); }
  catch (T &) {}
}

Note that if A's constructor throws an exception for which there is no handler, and the implementation chooses to call std::terminate before unwinding the stack, the destructor of __cleanup is not run, so your operator delete(void*, int, int, int) is not run.


        {
           
operator delete(p, 1, 2, 3);
       
}
       
delete p;  // this will invoke the usual operator delete(void*)
   
}
   
catch (std::bad_alloc&) { exit(1); }
   
catch ( T& ) {  }

}

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Belloc

unread,
Dec 1, 2015, 7:16:10 PM12/1/15
to ISO C++ Standard - Discussion


On Tuesday, December 1, 2015 at 8:21:41 PM UTC-2, Richard Smith wrote:

No, this was not in your original program, and is not implied by the semantics of the new-expression. Here's a more accurate lowering of your original code:

int main() {    
  try {
    A *p;
    {
      void *q = operator new(sizeof(A), 1, 2, 3);
      struct cleanup {
        void *p; int a; int b; int c;
        ~cleanup() { operator delete(p, 1, 2, 3); }
      } __cleanup = {q, 1, 2, 3};
      p = new (q) A; // call constructor of A
    }

    delete p;  // this will invoke the usual operator delete(void*)
  }
  catch (std::bad_alloc &) { exit(1); }
  catch (T &) {}
}

Note that if A's constructor throws an exception for which there is no handler, and the implementation chooses to call std::terminate before unwinding the stack, the destructor of __cleanup is not run, so your operator delete(void*, int, int, int) is not run.

I'm beginning to see the light now. I think I can understand your pseudo-code, but there's something that I missing: why would compiler implementers decide for such a convoluted code? In other words, what this pseudo-code is doing that mine is lacking or mistaken?

Richard Smith

unread,
Dec 1, 2015, 8:46:53 PM12/1/15
to std-dis...@isocpp.org
It wouldn't. This is pseudocode, not what compilers actually do (which cannot be expressed in C++).

In other words, what this pseudo-code is doing that mine is lacking or mistaken?

It's what it's *not* doing: it doesn't introduce a handler.

Greg Marr

unread,
Dec 1, 2015, 10:31:42 PM12/1/15
to ISO C++ Standard - Discussion
On Tuesday, December 1, 2015 at 3:55:10 PM UTC-5, Belloc wrote:
On Tuesday, December 1, 2015 at 6:45:13 PM UTC-2, Greg Marr wrote:

Where is the handler for the T that is thrown in the A constructor?  Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.

Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.  

int main()
{    
   
try
   
{
        A
* p = operator new(sizeof(A), 1, 2, 3);
       
try
       
{
            p
->A();        
       
}
       
catch ( T& )            // the throw is caught here
        {
           
operator delete(p, 1, 2, 3);
       
}

It's caught there, but then rethrown by the the "throw;" line following operator delete, which you forgot again.  This is what I was referring to.

Again, that is if the compiler did generate code like that, which as Richard says, it does not.

Belloc

unread,
Dec 2, 2015, 3:14:14 AM12/2/15
to ISO C++ Standard - Discussion


On Tuesday, December 1, 2015 at 11:46:53 PM UTC-2, Richard Smith wrote:

It's what it's *not* doing: it doesn't introduce a handler.

I can understand that the standard allows for the implementation to terminate without the stack being unwound, if an exception is not caught by a handler. However, why is this so important? Why not let the stack to unwind in this case? The code is going to be terminated anyway. 

I'm speculating here, but maybe the answer to this question is that, it simplifies the code for implementers. Could you confirm this? 

Giovanni Piero Deretta

unread,
Dec 2, 2015, 8:40:03 AM12/2/15
to ISO C++ Standard - Discussion

One, possibly primary, reason is ease of debugging. Unwinding the stack would cause a lot of state to be destroyed.

-- gpd

Greg Marr

unread,
Dec 2, 2015, 9:37:29 AM12/2/15
to ISO C++ Standard - Discussion
The standard DOES allow for the stack to unwind. It also allows for the stack to not unwind, at the implementer's choice. You can phrase this question in the opposite way just as easily.

I can understand that the standard allows for the implementation to unwind the stack, if an exception is not caught by a handler. However, why is this so important? Why let the stack unwind in this case? The code is going to be terminated anyway. 

Since the process is dying, why bother doing all the work of unwinding the stack?  What if it's dying because the program is in a bad state and an unwind is going to cause more damage?  Why waste compute resources trying to keep the system in a consistent state as it is in the process of dying?

Belloc

unread,
Dec 2, 2015, 1:42:44 PM12/2/15
to ISO C++ Standard - Discussion
My thanks to all who participated in this discussion. I've learned quite a bit since I posted this question in Stackoverflow.

Thiago Macieira

unread,
Dec 2, 2015, 6:50:10 PM12/2/15
to std-dis...@isocpp.org
On Wednesday 02 December 2015 06:37:29 Greg Marr wrote:
> I can understand that the standard allows for the implementation to unwind
> the stack, if an exception is not caught by a handler. However, why is this
> so important? Why let the stack unwind in this case? The code is going to
> be terminated anyway.
>
> Since the process is dying, why bother doing all the work of unwinding the
> stack? What if it's dying because the program is in a bad state and an
> unwind is going to cause more damage? Why waste compute resources trying
> to keep the system in a consistent state as it is in the process of dying?

Actually, some might argue that it's nice to have the last chance to save
state and do clean shutdowns, especially of out-of-process locks (e.g., remove
a lock file).

My counter-argument is that we're talking about an unexpected exception. That
means the program is already in an unexpected state, as it threw something the
developer had never expected to be thrown. Therefore, continuing execution can
prove to be even more damaging than an early termination.

That goes along with the argument that all exceptions that the code can
possibly ever throw under legitimate conditions must have a corresponding
catch handler. Using catch (...) or letting terminate() get called for some
legitimate exceptions should be avoided as developer laziness. And if
exception specifications hadn't been removed, virtual overrides should enforce
covariance of the exception classes.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Patrice Roy

unread,
Dec 2, 2015, 9:24:54 PM12/2/15
to std-dis...@isocpp.org
There are cases for catch(...) at main()-level, including using pre-allocated resources to signal a very bad situation to perform last resort actions. It's not the common case, but it's worth supporting. It's more a case of «Okay, before we die, let's...» than a case of exception handling, of course :)

Thiago Macieira

unread,
Dec 2, 2015, 9:57:27 PM12/2/15
to std-dis...@isocpp.org
On Wednesday 02 December 2015 21:24:50 Patrice Roy wrote:
> There are cases for catch(...) at main()-level, including using
> pre-allocated resources to signal a very bad situation to perform last
> resort actions. It's not the common case, but it's worth supporting. It's
> more a case of «Okay, before we die, let's...» than a case of exception
> handling, of course :)

Right, but remember that you've already unwound the stack while in an
unexpected state before you got to your main()-level catch.

Patrice Roy

unread,
Dec 2, 2015, 10:22:11 PM12/2/15
to std-dis...@isocpp.org
As I said, it's a chance to react to soon-dying, not a moment to handle the exception (it's generally a bit late for this :) )

Thiago Macieira

unread,
Dec 2, 2015, 11:42:54 PM12/2/15
to std-dis...@isocpp.org
On Wednesday 02 December 2015 22:22:10 Patrice Roy wrote:
> As I said, it's a chance to react to soon-dying, not a moment to handle the
> exception (it's generally a bit late for this :) )

But my argument stands: sine you're in an unexpected state, your main()-level
catch-all may make things worse.

Patrice Roy

unread,
Dec 3, 2015, 8:11:10 AM12/3/15
to std-dis...@isocpp.org
The use-case I mentioned for this relies on preallocated resources (a static buffer with a ready-to-send message to sone sysadmin or what-have-you); I'm just saying there's interesting (yet rare and specific) use-cases, nothing more.

Cheers!

Thiago Macieira

unread,
Dec 3, 2015, 12:34:46 PM12/3/15
to std-dis...@isocpp.org
On Thursday 03 December 2015 08:11:08 Patrice Roy wrote:
> The use-case I mentioned for this relies on preallocated resources (a
> static buffer with a ready-to-send message to sone sysadmin or
> what-have-you); I'm just saying there's interesting (yet rare and specific)
> use-cases, nothing more.

And that is still falling afoul of my case.

The fact that you're unwinding the stack during an unexpected state in the
first place is enough reason to cause more damage. What you want is to install
a terminate handler that would use the preallocated resources like you
described.

For example, suppose the unexpected state is a corruption internals of the
mutex system, however it came about. If an exception is thrown from an
unexpected place, such as from std::mutex;:unlock(), there will usually be no
code to catch it. If that causes std::terminate() without stack unwinding,
you'll be able to use the terminate handler and simply stop the application
dead.

But if it causes stack unwinding, it's possible a destructor will try to lock
a mutex in order to release its resources. If that std::mutex::lock() throws
again, we have a double throw and throw during stack unwinding, which causes
std::terminate, but in a different place. Worse, since the internals are
corrupt, the lock() may completely misbehave and simply deadlock. In that
case, the program fails to exit at all, will still retain its resources, and
the other threads will keep on running, doing whatever.

And to make an even worse case scenario: the lock() may succeed without
actually locking the mutex, in which case the code trying to save data may be
racing other threads writing to the same data block, causing corruption. Then
it may succeed to write that data to disk, overwriting the previously valid
data, even if you have a good atomic file saving method.

Patrice Roy

unread,
Dec 3, 2015, 2:41:04 PM12/3/15
to std-dis...@isocpp.org
These are good recommendations for careful usage more than prescription against usage :) But I see where you're coming from.

Cheers!

Belloc

unread,
Dec 4, 2015, 1:19:14 PM12/4/15
to ISO C++ Standard - Discussion


On Tuesday, December 1, 2015 at 8:21:41 PM UTC-2, Richard Smith wrote:

No, this was not in your original program, and is not implied by the semantics of the new-expression. Here's a more accurate lowering of your original code:

int main() {    
  try {
    A *p;
    {
      void *q = operator new(sizeof(A), 1, 2, 3);
      struct cleanup {
        void *p; int a; int b; int c;
        ~cleanup() { operator delete(p, 1, 2, 3); }
      } __cleanup = {q, 1, 2, 3};
      p = new (q) A; // call constructor of A
    }

    delete p;  // this will invoke the usual operator delete(void*)
  }
  catch (std::bad_alloc &) { exit(1); }
  catch (T &) {}
}

Note that if A's constructor throws an exception for which there is no handler, and the implementation chooses to call std::terminate before unwinding the stack, the destructor of __cleanup is not run, so your operator delete(void*, int, int, int) is not run.


        {
           
operator delete(p, 1, 2, 3);
       
}
       
delete p;  // this will invoke the usual operator delete(void*)
   
}
   
catch (std::bad_alloc&) { exit(1); }
   
catch ( T& ) {  }

}



There seems to be a problem with your pseudo-code. See this example. The code aborts when A() doesn't throw. free() is called twice for the same address., i.e., both the placement and the usual operator delete are called in this case.

T. C.

unread,
Dec 4, 2015, 5:37:52 PM12/4/15
to ISO C++ Standard - Discussion


On Friday, December 4, 2015 at 1:19:14 PM UTC-5, Belloc wrote:

There seems to be a problem with your pseudo-code. See this example. The code aborts when A() doesn't throw. free() is called twice for the same address., i.e., both the placement and the usual operator delete are called in this case.

It's non-tested pseudocode, what do you expect? You actually need a dismiss-able scope guard or something equivalent (e.g., Alexandrescu's SCOPE_FAIL) that can distinguish between stack unwinding and normal destruction.

The following functions generate identical assembly with g++ -O2 (http://goo.gl/kX4GCc):

void* operator new(std::size_t, int, double);
void operator delete(void*, int, double);

struct A { A(); };
void f() { new(1, 2.0) A; }

void g() {
   
void* q = ::operator new(sizeof(A), 1, 2.0);
   
struct __cleanup {
     
void* p; int i; double d; bool active;
     
~__cleanup(){ if(active) ::operator delete(p, i, d); }
     
void dismiss() { active = false; }
   
} guard = {q, 1, 2.0, true};
   
::new(q) A;
    guard
.dismiss();
}


Tony V E

unread,
Dec 4, 2015, 8:24:10 PM12/4/15
to Patrice Roy
We had this at Adobe. We called it 'panic save'. Save the user's doc in a temp file (don't overwrite the named file because the current doc might be corrupted) then put up a message telling the user. We also had extra memory pre-allocated to help guarantee ‎that there would be enough memory to display the message. 

We did it in the catch() in main(), but it might make more sense to do it before unwinding so as to not risk corrupting the doc during unwinding.

Sent from my BlackBerry portable Babbage Device
From: Patrice Roy
Sent: Wednesday, December 2, 2015 10:22 PM
Subject: Re: [std-discussion] Invoking placement delete when object construction throws

Thiago Macieira

unread,
Dec 4, 2015, 10:30:52 PM12/4/15
to std-dis...@isocpp.org
On Friday 04 December 2015 20:24:05 Tony V E wrote:
> We had this at Adobe. We called it 'panic save'. Save the user's doc in a
> temp file (don't overwrite the named file because the current doc might be
> corrupted) then put up a message telling the user. We also had extra memory
> pre-allocated to help guarantee ‎that there would be enough memory to
> display the message.

Symbian also did that. It reserved 1 MB of video RAM on the Nokia N8 to
display the out-of-memory dialog.

So when the camera application was running in the background and consuming 30
of the 32 MB of VRAM, the reserved memory for the dialog was 50% of the memory
available to applications.

Belloc

unread,
Dec 5, 2015, 6:24:58 AM12/5/15
to ISO C++ Standard - Discussion


On Friday, December 4, 2015 at 8:37:52 PM UTC-2, T. C. wrote:

It's non-tested pseudocode, what do you expect? You actually need a dismiss-able scope guard or something equivalent (e.g., Alexandrescu's SCOPE_FAIL) that can distinguish between stack unwinding and normal destruction.

The following functions generate identical assembly with g++ -O2 (http://goo.gl/kX4GCc):

void* operator new(std::size_t, int, double);
void operator delete(void*, int, double);

struct A { A(); };
void f() { new(1, 2.0) A; }

void g() {
   
void* q = ::operator new(sizeof(A), 1, 2.0);
   
struct __cleanup {
     
void* p; int i; double d; bool active;
     
~__cleanup(){ if(active) ::operator delete(p, i, d); }
     
void dismiss() { active = false; }
   
} guard = {q, 1, 2.0, true};
   
::new(q) A;
    guard
.dismiss();
}



What's the point of calling guard.dismiss() just before exiting g(), when A() doesn't throw?  After all, the object guard will be destroyed when the function ends, as the stack is unwound. That is, you won't be able to read guard.active after exiting g(). As far as I can understand, this pseudo-code has the same problem as the one shown by  Richard Smith.

T. C.

unread,
Dec 5, 2015, 6:49:19 AM12/5/15
to ISO C++ Standard - Discussion
Read ~__cleanup() again.
 

Belloc

unread,
Dec 5, 2015, 7:07:15 AM12/5/15
to ISO C++ Standard - Discussion


On Saturday, December 5, 2015 at 9:49:19 AM UTC-2, T. C. wrote:
Read ~__cleanup() again.

Great answer T.C. As you probably already know, you have my greatest respect for your precision and objectivity. Thanks again for the help. 

Belloc

unread,
Dec 5, 2015, 7:13:00 AM12/5/15
to ISO C++ Standard - Discussion
But I would still insist on a small correction to your pseudo-code:

~__cleanup(){ if(active) ::operator delete(p);

Am I correct?

T. C.

unread,
Dec 5, 2015, 7:50:48 AM12/5/15
to ISO C++ Standard - Discussion
No, the whole point of that example is to illustrate when/how placement delete is called.

 

Belloc

unread,
Dec 5, 2015, 8:08:22 AM12/5/15
to ISO C++ Standard - Discussion


On Saturday, December 5, 2015 at 10:50:48 AM UTC-2, T. C. wrote:

No, the whole point of that example is to illustrate when/how placement delete is called.

Yeah, you're right. Again, my distraction confused me. You're completely right. 
 
Reply all
Reply to author
Forward
0 new messages