C++ 异常处理的开销

1.简介

异常是 C++ 有别于 C 的一大特性 ,异常处理机制给开发人员处理程序中可能出现的意外错误带来了极大的方便,但为了实现异常,编译器会引入额外的数据结构与处理机制,增加了系统的开销。天下没有免费的午餐,使用异常处理时我们必须了解其带来的开销和问题。

C++ 异常处理使用 try、catch 和 throw 三个关键词来完成。

  • try 块是用来包含可能引发异常的代码块。当 try 块中的代码执行时,如果发生了异常,程序将立即跳转到匹配的catch块,而不会继续执行try块中的剩余代码。

  • catch 块用于捕获和处理 try 块中引发的异常。每个 catch 块可以捕获特定类型的异常,当匹配的异常发生时,相应的 catch 块将执行。通常,catch 块会处理异常并执行相应的操作,如日志记录、资源释放或异常信息的传递。

  • throw 关键字用于引发异常。当程序中的某个条件导致错误或异常情况发生时,你可以使用throw来抛出一个异常对象,以表示该异常情况。异常对象通常是派生自 std::exception 的自定义异常类的实例。

在程序执行过程中,异常处理流程大致如下:当函数体内某处发生异常(throw 异常)时,会检查该异常发生的位置是否在当前函数的某个 try 块之内,如果在的话,那么就需要找出与该 try 块配套的 catch 块。如果 catch 不匹配或者不在当前函数的某个 try 块的话,则沿着函数调用链逐层向上查找。当回退到上一层函数后,重复前面的操作。

2.开销

为了能够成功捕获异常和正确完成栈回退(stack unwind),C++ 引 入了相应的处理机制以及 TRYBLOCK、CATCHBLOCK 和 UNWINDTBL 数据结构来保存异常处理。我们首先来看看引入了异常处理机制的栈框架。
这里写图片描述
在每个 C++ 函数的栈框架中都多了一些与异常处理相关的数据,仔细观察的话,多出来的东西正好是一个 EXP 类型的结构体,这是一个典型的单向链表结构:

(1)piPrev 成员指向链表的上一个节点,它主要用于在函数调用栈中逐级向上寻找匹配的 catch 块,并完成栈回退工作;

(2)piHandler 成员指向完成异常捕获和栈回退所必须的数据结构(主要是两张记载着关键数据的表:try块表tblTryBlocks 及栈回退表tblUnwind);

(3)nStep 成员用来定位 try 块,以及在栈回退表中寻找正确的入口。其中EXP类型的结构体是一个单向链表式结构,用于完成异常回溯捕获以及栈回退清理工作。

一般来说,使用异常处理,因为异常处理信息的加入,除了会降低程序执行速度,也会导致编译后生成的程序偏大。

异常处理除了上面涉及的时间与空间开销,使用时也会带来如下问题:

(1)项目中使用异常,需要考虑与未使用异常的第三方和旧项目代码的整合问题,避免出现异常安全问题。
(2)异常使用不当,容易造成内存泄漏和程序崩溃,比如函数内抛出异常需要注意栈展开导致的内存泄露,析构函数抛出异常将程序置于不确定状态等。
(3)异常的跳转会彻底扰乱程序的执行流程并难以判断,给代码调试和维护增加难度。
(4)为保证写出异常安全的代码,往往需要借助其它特性,如智能指针,这进一步加剧了代码可读性的恶化与程序的时空开销,包括编译时间延长,运行效率降低以及代码尺寸增大。

3.小结

异常处理是 C++ 中十分有用的新特性之一,在大多数情况下,有着优异的表现和令人满意的时空效率。但使用异常时,我们要充分意识到异常带来和开销和需要注意的问题,综合考虑之下,再谨慎使用。


参考文献

改善C++程序的150个建议[M].李健.建议69:熟悉异常处理的代价
C++异常机制的实现方式和开销分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值