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


84

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

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


4
阅读这篇文章将帮助您
艾哈迈德说

我不知道-但是我猜C ++规范有明确的定义。(不过我可能错了)
保罗·内森

2
不,规范没有给出定义。它决定行为,而不是执行。保罗,你可能需要指定你感兴趣的哪一个实现。
罗布·肯尼迪


Answers:


49

实现可能有所不同,但是从需求中可以得出一些基本思想。

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

关于控制的实际转移,存在两种策略。一种是在堆栈本身中记录足够的信息以展开堆栈。这基本上是要运行的析构函数和可能捕获异常的异常处理程序的列表。如果发生异常,请运行执行那些析构函数的堆栈,直到找到匹配的catch。

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


4
AFAIR g ++使用第二种地址表方法,大概是出于与C兼容的原因。MicrosoftC ++编译器使用组合方法,因为其C ++异常建立在SEH(结构化异常处理)之上。在每个C ++函数中,MSC ++都会创建并注册一个SEH异常处理记录,该记录指向该表中具有该尝试函数的try-catch块和析构函数的地址范围的表。throw将C ++异常打包为SEH异常并调用RaiseException(),然后SEH将控制权返回给C ++特定的处理程序例程。
2009年

1
@Anton:是的,它使用地址表方法。有关详细信息,请参见我在stackoverflow.com/questions/307610/…上对另一个问题的回答。
CesarB

谢谢你的回答。您会看到C纯粹主义者可能会对C ++及其例外感到恐惧。简单的try / catch可以在运行时无意间创建许多堆栈对象或用多余的表膨胀程序的想法是嵌入式系统经常避免使用它们的原因。
speedplane

@speedplane:不,更多是由于缺乏了解。错误处理从来都不是免费的。C只是强迫您自己编写。我们都知道有很多C程序是如何失踪free()fclose()在一些很少使用的代码路径。
MSalters

@MSalters我不同意,这几乎完全是缺乏理解。工程师通常不了解异常如何工作以及异常将如何影响他们的代码,这有理由地导致在使用异常时犹豫。如果更清楚地传达了异常处理实现(并且看起来并不神奇),那么使用它们就会变得毫不犹豫。
speedplane

20

在15.1抛出标准例外中定义。

抛出将创建一个临时对象。
未指定此临时对象的内存分配方式。

创建临时对象后,控件将传递给调用堆栈中最接近的处理程序。解开掷点和接球点之间的堆栈。随着堆栈展开,所有堆栈变量都以相反的创建顺序销毁。

除非重新抛出异常,否则临时文件将在被捕获的处理程序结尾处销毁。

注意:如果按引用捕获,则引用将引用临时对象;如果按值捕获,则将临时对象复制到值中(因此需要复制构造函数)。

来自S.Meyers的建议(按const引用进行捕获)。

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

3
尚不清楚的是程序如何展开堆栈以及程序如何知道“最近的处理程序”在哪里。我很确定Borland在一种实现方法上拥有专利。
罗伯·肯尼迪

只要按照创建的相反顺序销毁对象,则除非您是编译工程师,否则实现细节并不重要。
马丁·约克

1
投票:a)“斯科特·迈耶斯”,而不是“ S.迈尔斯”;b)引用不真实:“有效的C ++”:“项目13:通过引用捕获异常 ”。这将允许将信息调整/附加到异常对象。
塞巴斯蒂安·马赫

3
@phresnel:不要忘记项目21:“尽可能使用const”。没有很好的理由来调整异常。您应该是a)“修复并丢弃”,b)重新抛出或c)生成新异常。
马丁·约克

1
@phresnel:是的,您有您的理由(不同意您的逻辑),我有我的观点,尽管我不会声称曾与他们讨论此特定主题,或者实际上不了解他们的想法(迈耶斯,亚历山大·库斯科和萨特),但我相信我的解释是正确的。但是,如果您在西雅图地区,那么您可以与这三个人聊天,因为他们是西北C ++用户组的常客(迈耶的频率比其他人低)。
马丁·约克

13

您可以在这里查看详细说明。

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.