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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/490773/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:38:23  来源:igfitidea点击:

How is the C++ exception handling runtime implemented?

c++exceptionerror-handlinglanguage-implementation

提问by Paul Joseph

I am intrigued by how the C++ exception handling mechanism works. Specifically, where is the exception object stored and how does it propagate through several scopes until it is caught? Is it stored in some global area?

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

Since this could be compiler specific could somebody explain this in the context of the g++ compiler suite?

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

采纳答案by MSalters

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.

异常对象本身是在一个函数中创建的对象,在其调用者中销毁。因此,在堆栈上创建对象通常是不可行的。另一方面,许多异常对象并不是很大。因此,如果确实需要更大的异常对象,可以创建例如一个 32 字节的缓冲区并溢出到堆。

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.

第二个策略将此信息移动到堆栈外的表中。现在,当发生异常时,调用堆栈用于找出进入但未退出的范围。然后在静态表中查找它们以确定抛出的异常将在何处处理,以及在它们之间运行哪些析构函数。这意味着堆栈上的异常开销更少;无论如何都需要返回地址。这些表是额外的数据,但编译器可以将它们放在程序的按需加载段中。

回答by Martin York

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

这在 15.1 抛出标准的异常中定义。

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

throw 创建一个临时对象。
如何分配此临时对象的内存未指定。

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.

创建临时对象后,控件将传递给调用堆栈中最近的处理程序。在 throw 和 catch 点之间展开堆栈。当堆栈展开时,任何堆栈变量都以与创建相反的顺序销毁。

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).

来自 S.Meyers 的建议(Catch by const reference)。

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

回答by Eduard - Gabriel Munteanu

You could take a look herefor 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.

看一看在普通 C 中使用的一个技巧来实现某种基本的异常处理,也可能会有所帮助。这需要以下列方式使用 setjmp() 和 longjmp():前者保存堆栈以标记异常处理程序(如“catch”),而后者用于“抛出”一个值。“thrown”值被视为是从被调用函数返回的。当再次调用 setjmp() 或函数返回时,“try 块”结束。

回答by Jules May

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

我知道这是一个老问题,但是有一个很好的说明,在这里解释了每个 gcc 和 VC 中使用的方法:http: //www.hexblog.com/wp-content/uploads/2012/06/Recon- 2012-Skochinsky-Compiler-Internals.pdf