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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With