5

When a C++ exception is thrown and stack unwinding occurs, there needs to be some storage area where the exception object is kept alive, until after execution leaves the scope of the outer-most catch block.

But where is this storage space exactly? Looking for answers on stackoverflow, I find: https://stackoverflow.com/a/27259902/2923952

This answer quotes the standard, saying:

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

... [Note: In particular, a global allocation function is not called to allocate storage for [...] an exception object (15.1). — end note]

That makes sense. You wouldn't want the compiler generating calls to malloc or new behind the scenes, since you may be writing code that has specific Allocator requirements.

Since the number of active exceptions is limited by the hard-coded stack depth, there doesn't seem to be any need to dynamically allocate space for exceptions anyway. The compiler could just have some stack-space or per-thread static storage to put active exceptions.

But now we have std::exception_ptr. This is basically a shared_ptr to an active exception object that keeps the exception object alive as long as any instances of the std::exception_ptr remain.

But the corollary of this that we can now indefinitely extend the lifetime of any active exception. So basically I could have a std::vector<std::exception_ptr>, and then in a loop I could keep throwing exceptions and storing each pointer to the current exception in my vector. So I could dynamically generate millions of active exceptions this way.

std::vector<std::exception_ptr> active_exceptions;
for (;;)
{
   try { throw int{}; }
   catch (...) { active_exceptions.push_back(std::current_exception()); }
}

This would then force the compiler to somehow dynamically add more storage to keep these exceptions alive. So how does it do this? Does it just fall back on using malloc/new after it runs out of static storage?

Siler
  • 8,976
  • 11
  • 64
  • 124
  • 1
    from https://en.cppreference.com/w/cpp/error/current_exception `"If the implementation of this function requires a call to new and the call fails, the returned pointer will hold a reference to an instance of std::bad_alloc."` so I guess it might create a copy of the exception on the heap – Alan Birtles Nov 02 '19 at 19:24

1 Answers1

3

The way storage for exceptions is allocated is implementation-defined. And implementations vary pretty widely on this. MSVC uses stack space for the exception when you throw/catch it (effectively making your stack shorter during unwinding). Other implementations in fact dynamically allocate memory for the exception (which itself can fail with an exception. That's fun).

However, implementations which don't dynamically allocate memory for the act of throw/catch will pretty much always perform a dynamic allocation whenever you use current_exception to get an exception_ptr to the exception. The semantics of this process essentially requires that the exception object live in a dynamic allocation, and you can see this in several aspects of the function.

For example, current_exception is said to return a reference to the exception object or a copy of it; which one happens is implementation-defined. This is also why current_exception itself can pseudeo-throw std::bad_alloc. That is, if memory allocation during current_exception fails, then instead of putting the current exception in the exception_ptr, std::bad_alloc is shoved in there instead.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    `__cxa_allocate_exception` calls `terminate` if it fails. (Construction of the exception object could fail by throwing an exception - that's another matter.) – T.C. Nov 03 '19 at 14:03