C++异常处理运行时是如何实现的?
Posted
技术标签:
【中文标题】C++异常处理运行时是如何实现的?【英文标题】:How is the C++ exception handling runtime implemented? 【发布时间】:2010-10-04 04:42:30 【问题描述】:我对 C++ 异常处理机制的工作原理很感兴趣。具体来说,异常对象存储在哪里以及它如何通过多个范围传播直到被捕获?它是否存储在某个全球区域?
由于这可能是特定于编译器的,有人可以在 g++ 编译器套件的上下文中对此进行解释吗?
【问题讨论】:
阅读此Article 会帮助你 我不知道——但我猜 C++ 规范有一个明确的定义。 (虽然我可能错了) 不,规范没有给出定义。它决定行为,而不是实施。 Paul,您可能想指定您感兴趣的实现。 相关问题:***.com/questions/307610/… 参考Technical Report on C++ Performance第5.4节 【参考方案1】:我知道这是一个老问题,但是有一个很好的阐述,在这里解释了 gcc 和 VC 中使用的方法:http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf
【讨论】:
【参考方案2】:这在 15.1 抛出标准异常中定义。
throw 会创建一个临时对象。 未指定此临时对象的内存分配方式。
在创建临时对象后,控件被传递给调用堆栈中最近的处理程序。在 throw 和 catch 点之间展开堆栈。当堆栈展开时,任何堆栈变量都会以与创建相反的顺序被销毁。
除非重新抛出异常,否则临时对象会在捕获它的处理程序结束时被销毁。
注意:如果通过引用捕获,则引用将引用临时对象,如果按值捕获,则将临时对象复制到值中(因此需要复制构造函数)。
来自 S.Meyers 的建议(通过 const 引用捕获)。
try
// do stuff
catch(MyException const& x)
catch(std::exception const& x)
【讨论】:
其他未指定的是如何程序展开堆栈以及如何程序知道“最近的处理程序”在哪里。我很确定 Borland 拥有一种实现该方法的专利。 只要对象以与创建相反的顺序被销毁,除非您是编译器工程师,否则实现细节并不重要。 投了反对票:a) “Scott Meyers”,而不是“S. Myers”; b) 不真实的引用:“Effective C++”:“第 13 项:通过 reference. 捕获异常”。这将允许调整/附加信息到异常对象。 @phresnel:不要忘记第 21 条:“尽可能使用 const”。调整异常没有好的案例。您应该 a) “修复和丢弃”,b) 重新抛出或 c) 生成新异常。 @phresnel:是的,你有你的理由(不同意你的逻辑),我有我的理由,虽然我不会声称与他们谈过这个具体主题或实际了解他们的想法(Meyers、Alexandrescu 和 Sutter) 我相信我的解释是有效的。但如果您在西雅图地区,那么您可以通过North West C++ User Group 与这三个人交谈(Meyers 的频率低于其他人)。【参考方案3】:实现可能会有所不同,但有一些基本的想法来自需求。
异常对象本身是在一个函数中创建的对象,在调用者中销毁。因此,在堆栈上创建对象通常是不可行的。另一方面,许多异常对象并不是很大。因此,如果实际需要更大的异常对象,可以创建例如 32 字节缓冲区并溢出到堆。
对于实际的控制权转移,存在两种策略。一种是在堆栈本身中记录足够的信息以展开堆栈。这基本上是要运行的析构函数和可能捕获异常的异常处理程序的列表。当异常发生时,返回执行这些析构函数的堆栈,直到找到匹配的 catch。
第二种策略将此信息移动到堆栈外的表中。现在,当发生异常时,调用堆栈用于找出进入但未退出的范围。然后在静态表中查找它们以确定抛出的异常将在哪里处理,以及在其间运行哪些析构函数。这意味着堆栈上的异常开销更少;无论如何都需要返回地址。这些表是额外的数据,但编译器可以将它们放在程序的按需加载段中。
【讨论】:
AFAIR g++ 使用第二种地址表方法,大概是出于与 C 兼容的原因。Microsoft C++ 编译器使用组合方法,因为它的 C++ 异常构建在 SEH(结构化异常处理)之上.在每个 C++ 函数中,MSC++ 创建并注册一个 SEH 异常处理记录,该记录指向一个表,其中包含该特定函数中的 try-catch 块和析构函数的地址范围。 throw 将 C++ 异常打包为 SEH 异常并调用 RaiseException(),然后 SEH 将控制权返回给 C++ 特定的处理程序例程。 @Anton:是的,它使用地址表方法。有关详细信息,请参阅我在 ***.com/questions/307610/… 对另一个问题的回答。 感谢您的回答。您可以看到 C 纯粹主义者如何害怕 C++ 及其例外。一个简单的 try/catch 会在运行时无意中创建大量堆栈对象或使用额外的表使程序膨胀,这就是嵌入式系统经常避免使用它们的原因。 @speedplane:不,这更多是由于缺乏理解。错误处理从来都不是免费的。 C 只是强迫你自己写。我们都知道有多少 C 程序在一些很少使用的代码路径中缺少free()
或fclose()
。
@MSalters 我并不反对,这几乎完全是缺乏理解。工程师通常不了解异常是如何工作的,以及异常将如何影响他们的代码,这会导致在使用异常时犹豫不决。如果异常处理实现更清晰地传达(并且看起来不像魔术),那么许多人就不会犹豫使用它们了。【参考方案4】:
您可以查看here以获得详细说明。
看看普通 C 语言中用于实现某种基本异常处理的技巧也可能会有所帮助。这需要以下列方式使用 setjmp() 和 longjmp():前者保存堆栈以标记异常处理程序(如“catch”),而后者用于“抛出”一个值。 “抛出的”值被看作是从被调用函数返回的。当再次调用 setjmp() 或函数返回时,“try 块”结束。
【讨论】:
以上是关于C++异常处理运行时是如何实现的?的主要内容,如果未能解决你的问题,请参考以下文章