我一直看到人们说异常很慢,但我从来没有看到任何证据 . 因此,我不会询问它们是否存在,而是询问异常如何在场景背后起作用,因此我可以决定何时使用它们以及它们是否很慢 .
据我所知,异常与做一堆返回是一回事,但它也会检查何时需要停止返回 . 它如何检查何时停止?我正在猜测并说有一个第二个堆栈,其中包含异常类型和堆栈位置然后返回直到它到达那里 . 我也猜测堆栈触摸的唯一时间是抛出和每次尝试/捕获 . 使用返回代码实现类似行为的AFAICT将花费相同的时间 . 但这是一个猜测,所以我想知道 .
例外如何真正起作用?
7 回答
您可以通过多种方式实现异常,但通常它们将依赖于操作系统的一些底层支持 . 在Windows上,这是结构化的异常处理机制 .
对代码项目的细节进行了不错的讨论:How a C++ compiler implements exception handling
出现异常的开销是因为编译器必须生成代码以跟踪在每个堆栈帧(或更精确的范围)中必须销毁哪些对象(如果异常传播出该范围) . 如果函数在堆栈上没有需要调用析构函数的局部变量,那么它应该在异常处理时不会有性能损失 .
使用返回代码一次只能展开堆栈的单个级别,而异常处理机制可以在一次操作中进一步向下跳转,如果在中间堆栈帧中没有任何内容可以执行 .
我决定用一小段C代码和一些旧的Linux安装来实际查看生成的代码,而不是猜测 .
我用
g++ -m32 -W -Wall -O3 -save-temps -c
编译它,并查看生成的程序集文件 ._ZN11MyExceptionD1Ev
是MyException::~MyException()
,因此编译器决定它需要析构函数的非内联副本 .惊喜!普通代码路径上根本没有额外的指令 . 编译器生成额外的外部修复代码块,通过函数末尾的表引用(实际上放在可执行文件的单独部分) . 所有工作都是由标准库在幕后完成的,基于这些表格(
_ZTI11MyException
是typeinfo for MyException
) .好吧,这对我来说实际上并不令人意外,我已经知道这个编译器是如何做到的 . 继续汇编输出:
在这里,我们看到了抛出异常的代码 . 虽然没有额外的开销只是因为可能抛出异常,但实际上抛出和捕获异常显然有很多开销 . 其中大部分隐藏在
__cxa_throw
内,必须:借助异常表遍历堆栈,直到找到该异常的处理程序 .
展开堆栈直到它到达该处理程序 .
实际上调用处理程序 .
将其与简单返回值的成本进行比较,您就会明白为什么异常只应用于异常退货 .
要完成,程序集文件的其余部分:
typeinfo数据 .
更多的异常处理表和各种额外信息 .
所以,结论,至少对于Linux上的GCC:成本是额外的空间(对于处理程序和表)是否抛出异常,加上在抛出异常时解析表和执行处理程序的额外成本 . 如果您使用异常而不是错误代码,并且错误很少,则可能会更快,因为您不再需要测试错误的开销 .
如果您需要更多信息,特别是所有
__cxa_
函数的功能,请参阅它们的原始规范:This article检查了这个问题,并且基本上发现在实践中存在异常的运行时成本,尽管如果不抛出异常,成本相当低 . 好文章,推荐 .
Matt Pietrek写了一篇关于Win32 Structured Exception Handling的精彩文章 . 虽然本文最初是在1997年编写的,但它今天仍然适用(但当然仅适用于Windows) .
我的一位朋友写了几年前Visual C如何处理异常 .
http://www.xyzw.de/c160.html
过去的例外情况很慢 was 在过去是如此 .
在大多数现代编译器中,这不再成立 .
注意:仅仅因为我们有异常并不意味着我们也不使用错误代码 . 当可以在本地处理错误时使用错误代码 . 当错误需要更多上下文进行更正时使用异常:我在这里更加雄辩地写了:What are the principles guiding your exception handling policy?
当没有使用异常时,异常处理代码的代价几乎为零 .
抛出异常时,会完成一些工作 .
但是你必须将它与返回错误代码和检查错误代码的成本进行比较一直回到可以处理错误的位置 . 写入和维护都更耗时 .
新手还有一个问题:
虽然异常对象应该很小,但有些人会在其中放入大量内容 . 然后您需要复制异常对象的成本 . 解决方案有两个方面:
不要在你的例外中加入额外的东西 .
通过const引用捕获 .
在我看来,我敢打赌,具有异常的相同代码要么更高效,要么至少与没有异常的代码相当(但是有所有额外的代码来检查函数错误结果) . 记住你没有得到任何免费的东西,编译器生成你应该首先编写的代码来检查错误代码(通常编译器比人类更有效) .
所有好的答案 .
另外,考虑调试代码的容易程度,这些代码在方法顶部执行“if checks”作为门,而不是允许代码抛出异常 .
我的座右铭是编写有效的代码很容易 . 最重要的是为下一个查看它的人编写代码 . 在某些情况下,这是你在9个月内,你不想诅咒你的名字!