微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > 异常机制简单探讨

异常机制简单探讨

时间:11-22 来源:互联网 点击:

引 言
我们在编写软件时不但要追求代码的正确性,更要关注程序的容错能力,在环境不正确或操作不当时不能死机,更不能造成灾难性后果。程序运行时有些错误是不可避免的,如内存不足、文件打开失败、数组下标溢出等,这时要力争做到排除错误,继续运行。
传统做法是返回一个错误代码,调用者通过if等语句测试返回值来判断是否成功。这样做有几个缺点:首先,增加的条件语句可能会带来更多的错误;其次,条件语句是分支点,会增加测试难度;另外,构造函数没有返回值,返回错误代码是不可能的。
C++的异常机制为我们提供了更好的解决方法。异常处理的基本思想是:当出现错误时抛出一个异常,希望它的调用者能捕获并处理这个异常。如果调用者也不能处理这个异常,那么异常会传递给上级调用,直到被捕获处理为止。如果程序始终没有处理这个异常,最终它会被传到C++运行环境,运行环境捕获后通常只是简单地终止这个程序。异常机制使得正常代码和错误处理代码清晰地划分开来,程序变得非常干净并且容易维护。
但是如何合理地使用异常机制来达到预期的效果呢?MISRA C++给出了一些推荐的规则,帮助程序员更加合理、可靠地实现异常机制。下面将结合这些规则对异常机制进行简单的探讨。

1 在恰当的场合使用恰当的特性
MISRA C++对异常的第1条规则就是:
规则15-0-1(不容讨论):异常机制只能用来处理错误。
异常处理的本质是控制流程的转移,但异常机制是针对错误处理的,仅在代码可能出现异常的情况下使用,不能用来实现普通的流程转移。
例如:


语法不会阻止你这样做,但杀鸡焉用牛刀。这样不但会降低程序的可读性,也会带来更大的开销。实际上,用一个简单的if语句就可以实现上述逻辑。同样,出于程序流程的清晰性考虑的还有:
规则15-0-3(强制):不允许通过goto或者switch语句跳转到try或catch语句块内。

2 正确地抛出异常
什么时候,什么地方,抛出什么样的异常,都是需要仔细考虑的。MISRA C++对此也作了相关规定。首先,来看一下抛出异常对象的类型中有哪些需要注意的地方。规则15-0-2(推荐):抛出的异常对象不应该是指针类型。
如果抛出的异常对象是个指针类型,指向的是动态创建的对象,那么这个对象应该由哪个函数来负责销毁,什么时候销毁,都很不清楚。比如说,如果是在堆中建立的对象,那通常必须删除,否则会造成资源泄漏;如果不是在堆中建立的对象,通常不能删除,否则程序的行为将不可预测。
规则15-1-2(强制):不能显式地把NULL作为异常对象抛出。
因为throw(NULL)=tbrow(0),因此NULL会被当作整型捕获,而不是空指针常量,这可能与程序员的预期不一致。
通常,很多函数都是基于function-try-block结构的,即函数体整个包含在一个函数try块中。而函数能抛出什么类型的异常对象,有以下规定:
规则15-5-2(强制):如果一个函数声明时指定了具体的异常类型,那么它只能抛出指定类型的异常。
规则15-4-1(强制):如果一个函数声明时指定了异常的类型,那么在其他编译单元里该函数的声明必须有同样的指定。
函数的代码结构如下:返回值类型函数名(形参表)throw(类型名表){函数体}
如果函数在声明时没有异常规范,那么它可以抛出任意类型的异常对象;如果异常类型为空,则表示不抛出任何类型异常。注意这两者之间的区别,前者指没有throw(类型名表)语句,而后者有throw(类型名表),只是类型名表为空。但如果声明时指定了异常的类型,那么它只能抛出指定类型的异常。
另外,函数原型中的异常声明要与实现中的异常声明一致,否则会引起异常冲突。由于异常机制是在运行出现异常时才发挥作用的,因此如果函数的实现中抛出了没有在其异常声明列表中列出的异常,编译器也许不能检查出来。当抛出一个未在其异常声明列表里的异常类型时,unexpected()函数会被调用,默认会导致std::bad_exception类型的异常被抛出。如果std::bad_exception不在异常声明列表里,又会导致terminate()被调用,从而导致程序结束。
对于什么时候能抛出异常,则有以下规定:
规则15-3-1(强制):异常只能在初始化之后而且程序结束之前抛出。
在执行main函数体之前,是初始化阶段,构造和初始化静态对象;在main函数返回后,是终止阶段,静态对象被销毁。在这两个阶段中如果抛出异常,会导致程序以不定的方式终止(这依赖于具体的编译器)。例如:

在这个例子中,catch块只能捕获上面try块中的异常。如果在对象c的构造函数或析构函数中抛出异常,并不能被main里的catch块捕获,而且会导致程序终止。
除了上述规则,还有以下两个规则需要注意:
规则15-1-1(强制):throw语句中的表达式本身不能引发新的异常。
如果在构造异常对象,或者计算赋值表达式时引发新的异常,那么新的异常会在本来要抛出的异常之前被抛出,这与程序员的预期不一致。
规则15-1-3(强制):空的throw语句只能出现在catch语句块中。
空的throw用来将捕获的异常再抛出,可以实现多个处理程序问异常的传递。然而,如果在catch语句外用,由于没有捕获到异常,也就没有东西可以再抛出,这样会导致程序以不定的方式终止(这依赖具体的编译器)。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top