Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does compiler manage storage for exceptions in light of exception_ptr

Tags:

c++

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?

like image 859
Siler Avatar asked Oct 15 '22 09:10

Siler


1 Answers

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.

like image 108
Nicol Bolas Avatar answered Nov 15 '22 10:11

Nicol Bolas