C语言异常处理
1、异常处理语句
try程序块:该区段包含可能会发生异常的代码,在发生了异常之后,通过throw抛出异常;
throw程序块: throw语句用于抛出异常,在C++中,被抛出的异常可以是内置类型的对象,也可以是自定义类型的对象;
catch程序块: 对异常进行捕捉,处理特定类型的异常。
例一:
#include<stdio.h>
#include<setjmp.h>
#include<signal.h>
#include<iostream>
using namespace std;
#include<string>
void test(){
try{
char* p = new char[0x7fffffff]; //抛出异常
}
catch(exception e){
cout << e.what() << endl;
}
}
int main(){
test();
return 0;
}
程序运行结果:
2、异常的抛出和捕获
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪一个处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常后会释放局部存储对象,所以被抛出的对象也就归还给系统了。
- throw表达式会初始化一个抛出特殊的异常对象的副本(匿名对象),异常对象由编译管理,异常对象再传给对应的catch处理之后撤销。
3、异常栈展开
3.1 异常结构体—EXP
- piPrev成员指向链表的上一个节点,它主要用于在函数调用栈中逐级向上寻找匹配的 catch 块,并完成栈回退工作;
- piHandler 成员指向完成异常捕获和栈回退所必须的数据结构(主要是两张记载着关键数据的表:“try”块表:tblTryBlocks及“栈回退表”:tblUnwind);
- nStep 成员用来定位 try 块,以及在栈回退表中寻找正确的入口;
【说明】:编译器会为每一个“C++函数”定义一个EHDL结构,不过只会为包含了“try”块的函数定义tblTryBlocks成员。此外,异常处理器还会为每个线程维护一个指向当前异常处理框架的指针。该指针指向异常处理器链表的链尾。
3.2 异常栈展开
例子:
#include<stdio.h>
#include<setjmp.h>
#include<signal.h>
#include<iostream>
using namespace std;
#include<string>
void testB(int a, int b){
//...
testA(a, b);
int n = 1;//next statement
//...
return;
}
void testA(int a,int b){
//...
return;
}
C++异常栈展开
3.3 异常栈展开过程
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句;
检查throw本身是否在try内部,如若是在查找匹配的catch语句;
如果有匹配的,则处理;若没有,则退出当前函数栈,继续在调用该函数的函数栈中查找;
不断重复上述过程,如果达到main函数的函数栈,依然没有找到,匹配的catch语句,则终止程序;
但是在函数外,该异常最终会由操作系统捕获并处理;
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开;
找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行;
3.4 栈回退(Stack Unwind)机制
“栈回退”是伴随异常处理机制引入C++中的一个概念,主要用来确保在异常被抛出、捕获并处理后,所有生命期已结束的对象都会被正确的析构,他们所占用的空间都会被正确的回收。
4、异常捕获机制
4.1 含义
异常结构体EXP中的 nStep 除了能够跟踪对象创建、销毁阶段以外,还能够标识当前执行点是否在 try 块中,以及(如果当前函数有多个 try 块的话)究竟在哪个 try 块中。这是通过在每一个 try 块的入口和出口各为 nStep 赋予一个唯一 ID 值,并确保 nStep 在对应 try 块内的变化恰在此范围之内来实现的。
在具体实现异常捕获时,首先,C++ 异常处理器检查发生异常的位置是否在当前函数的某个 try 块之内。这项工作可以通过将当前函数的 nStep 值依次在piHandler指向tblTryBlocks[]表的条目中进行范围为 [nBeginStep, nEndStep) 的比对来完成。
4.2 异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外:
- 允许从非const对象到const类型对象的转换;
- 允许从派生类类型到基类类型的转换;
- 将数组转换成指向数组类型的指针,将函数转换为指向函数类型的指针;
4.3 异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理之后,希望再次交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数来处理。
5、异常规范
在函数声明之后,列出该函数有可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
- 成员函数再累内声明和类外定义两处必须有相同的异常规范;
- 函数抛出一个没有列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),系统调用C++标准库中定义的函数unexpected();
- 如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中;
- 派生类的虚函数的异常规范必须基类虚函数的异常规范一样或者更严格(是基类虚函数的异常的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常规范;
【注】文章参考: