如何实现 C + + 异常处理运行时?

我对 C + + 异常处理机制是如何工作的很感兴趣。具体来说,异常对象存储在哪里,如何通过多个作用域传播,直到被捕获?是否存储在全球范围内?

既然这可能是特定于编译器的,有人能在 g + + 编译器套件的上下文中解释一下吗?

29032 次浏览

You could take a look here for a detailed explanation.

It may also help to take a look at a trick used in plain C to implement some basic sort of exception handling. This entails using setjmp() and longjmp() in the following manner: the former saves the stack in order to mark the exception handler (like "catch"), while the latter is used to "throw" a value. The "thrown" value is seen as if it has been returned from a called function. The "try block" ends when setjmp() is called again or when the function returns.

This is defined in 15.1 Throwing an exception of the standard.

The throw creates a temporary object.
How the memory for this temporary object is allocated is unspecified.

After creation of the temporary object control is passed to the closest handler in the call stack. unwinding the stack between throw and catch point. As the stack is unwind any stack variables are destroyed in reverse order of creation.

Unless the exception is re-thrown the temporary is destroyed at the end of the handler where it was caught.

Note: If you catch by reference the reference will refer to the temporary, If you catch by value the temporary object is copied into the value (and thus requires a copy constructor).

Advice from S.Meyers (Catch by const reference).

try
{
// do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}

Implementations may differ, but there are some basic ideas that follow from requirements.

The exception object itself is an object created in one function, destroyed in a caller thereof. Hence, it's typically not feasible to create the object on the stack. On the other hand, many exception objects are not very big. Ergo, one can create e.g a 32 byte buffer and overflow to heap if a bigger exception object is actually needed.

As for the actual transfer of control, two strategies exist. One is to record enough information in the stack itself to unwind the stack. This is basically a list of destructors to run and exception handlers that might catch the exception. When an exception happens, run back the stack executing those destructors until you find a matching catch.

The second strategy moves this information into tables outside the stack. Now, when an exception occurs, the call stack is used to find out which scopes are entered but not exited. Those are then looked up in the static tables to determine where the thrown exception will be handled, and which destructors run in between. This means there is less exception overhead on the stack; return addresses are needed anyway. The tables are extra data, but the compiler can put them in a demand-loaded segment of the program.

I know this is an old question, but there's a very good exposition, explaining both the methods used in each of gcc and VC here: http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf