异常与C++的异常
C++编程中有一个重要的机制就是异常的处理,标准的C++定义了try...catch..throw的异常处理机制。但是这只是C++里的异常。
广义的异常可以分为硬件产生的异常和软件产生的异常,像throw这样的动作就是在软件上产生一个异常,但是像写入一个非法内存、除0这些都是硬件产生的异常;
异常的处理可以分为结构化异常处理(Structured Exception Handling)SEH和向量异常处理(Vectored exception handling)VEH
C++里try...catch..throw属于SEH,但是他只能捕捉显示的throw出来的软件异常,明确这个问题非常重要。以前一直认为可以catch任何异常,其实这种想法是错的,比如写一句int* p=0;*p=1;这样的语句是catch不到的,直接就会crash了,因为这是内存错误,硬件异常。
这样看来,C++的try...catch..throw异常处理机制只是处理了所有异常中的一个子集而已。
然而在windows上开发的我们,尤其是去开发一些产品,我们希望有方法去处理任何异常,绝不让用户突然收到一个崩溃的感叹号,提高用户的体验怎么办?
原来window在系统层面上提供了更多的异常的处理机制,而C++的try...catch..throw也只是window系统的异常处理机制中被整合进去的一小块。
window系统层面的(结构化)异常处理机制
window是把所有的硬件和软件的异常都整合起来同等看待,无论是内存错误、用throw产生一个异常,都会触发操作系统对当前的线程的异常处理
每个线程在创建后,都会初始化一个数据结构:
;struct _EXCEPTION_REGISTRATION{
; struct _EXCEPTION_REGISTRATION *prev;
; void (*handler)(PEXCEPTION_RECORD,
; PEXCEPTION_REGISTRATION,
; PCONTEXT,
; PEXCEPTION_RECORD);
; struct scopetable_entry *scopetable;
; int trylevel;
; int _ebp;
; PEXCEPTION_POINTERS xpointers;
;}
很多个EXCEPTION_REGISTATION 连成一个list L
其中的每个节点包含了一个handler成员,他就是具体的对异常进行处理的操作(可以猜测catch后的内容就是最终被整合到这个函数里的)
当一个异常E发生后,系统会寻找这个线程的L,从头开始,知道寻找到能对E进行处理的那个节点EXCEPTION_REGISTATION,对其进行处理。
对于C++的try catch其实就是在try的时候将L里面建立一个新的节点,用于捕捉某个异常。
所以win的异常处理机制的核心就在于每个线程内的这个EXCEPTION_REGISTATION 的list。
这里有个问题,就是当一个异常没有再list寻找到相应的节点怎么办,其实这个list会在尾端放置一个默认的异常处理单元,当查询到尾端时就一定调用这个处理函数。
所以我们就有一种办法去截获所有程序中我们无法catch的异常(硬件或软件),就是去自定义这个处理函数,使用API SetUnhandledExceptionFilter
万能的异常捕获器 SetUnhandledExceptionFilter
记住这个函数只有在release版本生效!
win程序在release情况下,发生了异常,并且没有被捕捉处理,就会调用一个默认的UnhandledExceptionFilter函数,这个函数还传入一个参数_EXCEPTION_POINTERS*,里面有crash的一些信息。
如果我们想自定义这个处理方法,就可以自定一个函数如
long __stdcall MyUnhandledExceptionFilter (_EXCEPTION_POINTERS* ExceptionInfo){。。。
}
然后使用SetUnhandledExceptionFilter set进来。
MyUnhandledExceptionFilter 函数有三种返回值:
EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以结束了,这时执行完这个函数程序退出
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行 ,这时程序会试图在运行一遍刚才又问题的代码
通过自定义的异常处理函数,等于截获了程序出错的最后一道屏障,在程序crash即将关闭前改变了原有的行为,可以做到:
1.可以dump出来当前的信息,上传给服务器用于技术人员统计分析
2.可以用更加友善的界面告诉你的用户程序出错了,优雅的挂掉