Struct Exception handing ,SEH。简单的理解就是c++的try-catch 语句底层的实习那就是利用了编译器和操作系统的结构化异常处理功能。
终止处理(termination handing),异常处理(exception handing)
终止处理
__try {
}
__finally {
}
__finally{}会在三种中情况下被调用
- __try{}自然运行完毕代码走到__finally{}
-局部展开。 保证__try{}中的代码不管是以什么方式退出(除了线程进程结束函数)__finally{}中的代码都会被执行。 - 全局展开
可以调用内在函数(intrinsic function,就是被封装成宏的编译器关键字)AbnormalTermination查看,如果正常进入返回FALSE,反之TRUE;
局部展开
局部展开(local wnwind),缺点由额外的代码开销,所以我们最好不要在__try{}中写return ,goto,break,continue等语句。代替办法。
__try {
__leave;
}
__finally {
//
}
return ;
__leave使代码调到闭花括号上(})。来减少局部展开的开销。
还有注意一点。
__try {
return(100);
}
__finally {
return(101);
}
最终程序回返回101
异常处理
windows 的异常分为两种,一种是硬件异常,一种是软件异常。
硬件异常:这种异常都是致命错误程序逻辑错误,如非法的内存访问,除以0等。由cpu 抛出来提醒我们修改程序来避免这种问题。
软件异常:这些是我们定义的异常,表示一些不可抗逆的因素被抛出,以让开发人员写出应对策略。比如我们找的文件不存在,你给我的数据由问题等
硬件异常
__try {
}
__except(EXCEPTION_*) {
//exception handler
}
__except()中我们可以填入下面三个标志,要包含excpt.h头文件
EXCEPTION_EXECUTE_HANDLER:运行__except代码块的内容,进行全局展开。然后接着_except代码块之后的代码运行。
EXCEPTION_EXECUTE_SEARCH:不运行此__except块代码,然后寻找外面一次的__try的_except代码块,如果没有,再在更外面的一层寻找,直到找到__try的_except代码块,然后运行它。
EXCEPTION_CONTINUE_EXECUTE:运行完__except代码之后回到抛出异常的cpu指令。因为是和cpu指令有关所以最好不要使用这个
Note:
__except()中可以是任何代码,但是必须染回的EXCEPTION_*标志。
LONG select(){
return EXCEPTION_EXECUTE_HANDLER;
}
void fun(){
__try {
}
__except (select()) {
}
}
全局展开
global unwind,全面展开。当内部的__try块产生异常的时候。如果内部__try没有_except块时,会向外查找外部的__try,如果该__try块也没有_except块,就接着到更外面的__try块中寻找_except块。当找到_except块时,且如果其EXCEPTION_EXECUTE_HANDLER时,系统会从栈底部开始向上检索开始之前没有_except块的__try块的__finally块,并运行它们。
void in() {
__try {
// throw exception
}
__finally {
}
}
void out(){
__try {
in();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
}
}
in 中异常被抛出。当执行out 的__except (EXCEPTION_EXECUTE_HANDLER) {}之前,会调用之前进过的__try{}的__finally { }块。
Note:
从windows vista开始如果,当查找相应的__try{}__except{}的时。时找不到__except{}。进程就会立即停止。__finally也不会执行。全局展开被打断了。
停止全局展开
停止全局展开。也就是在执行最底层的__finally{}之后,系统会当作异常好像没发生一样接着运行。举个例子
void in() {
__try {
// throw exception
}
__finally {
//#1
}
}
void mid(){
in();
// #2 some code
}
void out(){
__try {
mid();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
//#3
}
//#4
这是一个三层的调用结构。out 调用 mid,mid 调用in。现在如果in抛出异常的话。我们的#1,#3会被执行。然后在到#4。
现在我们让这个全局展开停止。
void in() {
__try {
// throw exception
}
__finally {
//#1
return ;
}
}
void mid(){
int out();
// #2 some code
}
void out(){
__try {
mid();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
//#3
}
//#4
让 全局展开停止很简单,就是在__finally {}中加入return语句。然我们来看看会发生什么。
这个时候。in 抛出函数。然后它就寻找有没有__except (EXCEPTION_EXECUTE_HANDLER) {}。接着回到in的__finally{}块中执行#1然后运行return。现在全局展开停止了,系统会当异常没发生一样in函数出栈。运行mid函数的#2部分。然后mid 出栈,运行#4的部分。
也就晕写了#1,#2,#4.但是没有运行#3.就好像异常没发生一样。
Note:
windows 把SEH设计成这样是做过充分考虑的。所以我们也不能说什么。但是要注意这种特性并不一定时你想要的。所以在__finally{}中写return 时编译器会给你一个警告来提醒你。
GetExeptionCode
说完了,如何处理硬件异常你会返现所有的硬件异常都会被送往__except ()来处理但是我们不知道是什么具体的硬件异常啊。所以windows 在excpt.h中提供了GetExeptionCode宏来获得具体的硬件异常。能返回的值太多了我就不列了,请自行参考MSDN
https://msdn.microsoft.com/en-us/library/yk5ztsez(v=vs.90).aspx
返回值包含在winbase.h头文件中
需要注意一句话
The Microsoft C/C++ Optimizing Compiler interprets this function as a keyword, and its use outside the appropriate exception-handling syntax generates a compiler error.
意思是说这个宏的底部其实是使用一个编译器的关键字。在分析异常代码(__except的圆括号)之外使用它会产生编译器错误的
之后你就可以天马星空的在这个__except()的圆括号中写判断代码了,但是别忘了返回值要是EXCEPTION_*哦
软件异常
软件异常就是非cpu的异常。处理软件异常的好处就是,我们不需要每次调用WIN32API的时候都检测其返回值,这样效率不高且代码写的也很累。
所以WIN32API提供了异常处理的解决办法。例如HeapCreate函数正常情况下是通过返回值来判断是不是成功创建堆的。
但是如果我们向其传递HEAP_GENERATE_EXCEPTIONS标志时,函数就会在失败使抛出STATUS_NO_MEMORY
想抛出软件异常的时候只需要调用下面函数就行了
void WINAPI RaiseException(
_In_ DWORD dwExceptionCode,
_In_ DWORD dwExceptionFlags,
_In_ DWORD nNumberOfArguments,
_In_ const ULONG_PTR *lpArguments
);
dwExceptionCode
我们定义的错误吗。winerror.h中说的很清楚了
dwExceptionFlags
0:默认值
在__except()中使用EXCEPTION_CONTINUE_EXECUTTION时函数可以接着执行,但是和硬件异常不一样,windows 会让接着RaiseException后的代码执行
EXCEPTION_NONCONTINUABLE:
不能在__except()中使用EXCEPTION_CONTINUE_EXECUTTION,如果还是使用了,就会会抛出一个叫做EXCEPTION_NONCONTINUABLE_EXCEPTION的新异常。
lpArguments
nNumberOfArguments
附加信息和附加信息的元素,参数不能超过,EXCEPTION_MAXIMUM_PARAMETERS(winNT.h中定义)
对于处理软件异常和处理硬件异常时一样的
Note:
上面我们提到了在__finally{},__except(){}的圆括号和花括号中都有可能产生新的异常。
windows 怎么处理的呢。windows 会在发生异常的时候向线程栈中压入三个结构。
EXCEPTION_RECORD:包含与cpu无关的异常信息
CONTEXT:包含和cpu有关的cpu信息
EXCEPTION_POINTERS:指向上面两个信息
windows 提供一个宏GetExceptionInformation()返回EXCEPTION_POINTERS结构的地址。它是一个内在函数,不能在__except()之外使用。运行完__except()这些结构就会被销毁
EXCEPTION_RECORD是一个链表还包含另一个EXCEPTION_RECORD。而最前面的就是最近产生的异常