What happens if 'throw' fails to allocate memory for exception object?

From C++11 standard (15.1.p4):

The memory for the exception object is allocated in an unspecified way, except as noted in 3.7.4.1

What if allocation fails -- will it throw std::bad_alloc instead? Call std::terminate? Unspecified?


(providing my own answer... I'll wait for few days and if there are no problems with it -- I'll mark it as accepted)

I spent some time investigating this and here is what I unearthed:

  • C++ standard does not specify what is going to happen in this case
  • Clang and GCC seem to use C++ Itanium ABI

Itanimum ABI suggests to use heap for exceptions:

Storage is needed for exceptions being thrown. This storage must persist while stack is being unwound, since it will be used by the handler, and must be thread-safe. Exception object storage will therefore normally be allocated in the heap

...

Memory will be allocated by the __cxa_allocate_exception runtime library routine.

So, yeah... throwing an exception will likely involve locking mutexes and searching for a free memory block. :-(

It also mentions this:

If __cxa_allocate_exception cannot allocate an exception object under these constraints, it calls terminate()

Yep... in GCC and Clang "throw myX();" can kill your app and you can't do a thing about it (maybe writing your own __cxa_allocate_exception can help -- but it certainly won't be portable)

It gets even better:

3.4.1 Allocating the Exception Object

Memory for an exception object will be allocated by the __cxa_allocate_exception runtime library routine, with general requirements as described in Section 2.4.2. If normal allocation fails, then it will attempt to allocate one of the emergency buffers, described in Section 3.3.1, under the following constraints:

  • The exception object size, including headers, is under 1KB.
  • The current thread does not already hold four buffers.
  • There are fewer than 16 other threads holding buffers, or this thread will wait until one of the others releases its buffers before acquiring one.

Yep, your program can simply hang! Granted chance of this are small -- you'd need to exhaust memory and your threads need to use up all 16 emergency buffers and enter wait for another thread that should generate an exception. But if you do things with std::current_exception (like chaining exception and passing them between threads) -- it is not so improbable.

Conclusion:

This is a deficiency in C++ standard -- you can't write 100% reliable programs (that use exceptions). Text book example is a server that accepts connections from clients and executes submitted tasks. Obvious approach to handle problems would be to throw an exception, which will unwind everything and close connection -- all other clients won't be affected and server will continue to operate (even under low memory conditions). Alas, such server is impossible to write in C++.

You can claim that modern systems (i.e. Linux) will kill such server before we reach this situation anyway. But (1) it is not an argument; (2) memory manager can be set to overcommit; (3) OOM killer won't be triggered for 32-bit app running on 64bit hardware with enough memory (or if app artificially limited memory allocation).

On personal note I am quite pissed about this discovery -- for many years I claimed that my code handles out-of-memory gracefully. Turns out I lied to my clients. :-( Might as well start intercepting memory allocation, call std::terminate and treat all related functions as noexcept -- this will certainly make my life easier (coding-wise). No wonder they still use Ada to programs rockets.


[intro.compliance]/2 Although this International Standard states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:

(2.1) — If a program contains no violations of the rules in this International Standard, a conforming implementation shall, within its resource limits, accept and correctly execute that program.

Emphasis mine. Basically, the standard envisions failure to allocate dynamic memory (and prescribes behavior in this case), but not any other kind of memory; and doesn't prescribe in any way what the implementation should do when its resource limits are reached.

Another example is running out of stack due to a too-deep recursion. Nowhere does the standard say how deep a recursion is allowed. The resulting stack overflow is the implementation exercising its "within resource limits" right-to-fail.