结构化异常处理

结构化异常处理结构化异常处理 , 是Windows作业系统上 , Microsoft对C/C++程式语言做的语法扩展 , 用于处理异常事件的程式控制结构 。
异常事件是打断程式正常执行流程的不在期望之中的硬体、软体事件 。硬体异常是CPU抛出的如“除0”、数值溢出等;软体异常是作业系统与程式通过RaiseException语句抛出的异常 。
Microsoft扩展了C语言的语法 , 用 try-except与try-finally语句来处理异常 。异常处理程式可以释放已经获取的资源、显示出错信息与程式内部状态供调试、从错误中恢复、尝试重新执行出错的代码或者关闭程式等等 。
【结构化异常处理】一个__try语句不能既有__except , 又有__finally 。但try-except与try-finally语句可以嵌套使用 。
目录[隐藏]1 try-except
2 try-finally语句
3 例子
4 与Windows异常处理机制的关係
5 参考文献
try-except[编辑]__try {// 受保护执行的代码 } __except ( 过滤表达式 ) {// 异常处理代码 }首先 , __try複合语句中的受保护的代码被执行 。如果没有异常发生 , 则继续执行__except複合语句之后的代码 。如果__try複合语句中的受保护执行的代码发生了异常 , 或受保护执行的代码调用的函式内部发生了异常并要求调用者来处理该异常 , __except语句的过滤表达式(filter expression)被求值 , 根据其结果来决定如何处理异常:EXCEPTION_CONTINUE_EXECUTION (–1) : 导致异常的问题已经解决 , 在异常出现的现场重新执行操作 。
EXCEPTION_CONTINUE_SEARCH (0) :当前__except语句不能处理该异常 , 通知作业系统继续搜寻该执行绪其他的异常处理程式 。
EXCEPTION_EXECUTE_HANDLER (1):当前__except语句识别该异常 , 通过执行__except的複合语句来处理该异常 。然后执行__except複合语句之后的代码 。
内在函式GetExceptionCode返回一个32位整型值 , 表示异常的类型 。内在函式GetExceptionInformation返回异常的详细信息及现场信息(如CPU暂存器的值)。这两个函式可用于异常表达式中来判断是否处理该异常 。这里说的内在函式(intrinsic function) , 是指编译器提供内联(inline)实现的函式 。Windows作业系统的应用程式的main()函式受到结构化异常的try-except语句保护 , 因此程式的未被处理的异常都会被捕获 。这是因为 , Windows作业系统在载入用户进程时 , Kernel32.dll中的BaseProcessStart函式在__try块中调用了用户进程的入口函式mainCRTStartup , 因此用户程式的所有异常都会被捕获、得到处理 。可以使用C++运行时库中的_set_se_translator()函式把结构化异常转为抛出一个C++对象的C++异常 。此外 , C++异常的catch(...)语句也能直接捕捉结构化异常 。可以使用作业系统运行时库kernel32中的SetUnhandledExceptionFilter()函式设定顶层未处理异常过滤器(top-level unhandled exception filter) , 捕获进程的各个执行绪中一切未被处理的结构化异常 。该函式一般用于从特定的错误中恢复 , 如无效的调用栈(invalid stack) 。try-finally语句[编辑]__try {// 受保护执行的代码 } __finally {// 清理用途的代码 }__try複合语句中受保护的代码以任何方式执行结束后 , 不论__try複合语句是因为出现异常而非正常结束 , 还是没有出现异常而正常结束 ,  , __finally複合语句中的代码都会被执行 。如果__try複合语句中受保护的代码的执行没有出现异常 , 包括用goto语句或longjump系统函式跳出__try複合语句等情形 , 这时将执行__finally複合语句 , 然后执行try-finally语句之后的其他代码 。如果__try複合语句中受保护的代码执行中出现了异常 , 首先按函式调用顺序从新向旧搜寻所有包含了出现异常的执行点的try-except语句 , 执行每个except的过滤函式 , 直到某个try-except语句的过滤函式结果值为EXCEPTION_EXECUTE_HANDLER 。这时 , 再从包含异常出现的执行点的最内层try-finally语句开始由内向外执行每个finally块中的代码 , 直至回退到那个过滤函式结果值为EXCEPTION_EXECUTE_HANDLER的try-except语句执行其except块 , 最后执行该try-except语句后面的其他代码 。在__finally複合语句中使用内在函式AbnormalTermination判断是正常结束还是非正常结束__try複合语句 。例子[编辑]#include <stdio.h> #include <windows.h> // for EXCEPTION_ACCESS_VIOLATION #include <excpt.h>int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {printf("在异常表达式中\n");if (code == EXCEPTION_ACCESS_VIOLATION) {printf("接受处理访问违例异常\n");return EXCEPTION_EXECUTE_HANDLER;}else {printf("其他异常都不处理\n");return EXCEPTION_CONTINUE_SEARCH;}; }int main() {int* p = 0x00000000;// pointer to NULLprintf("开始主程式\n");__try{printf("进入外层的try\n");__try{printf("进入内层的try");int *p=0; // 空指针*p = 13;// 导致访问冲突异常}__finally{printf("在finally内部 。");printf(AbnormalTermination() ? "正常终止\n" : "非正常终止\n");}}__except(filter(GetExceptionCode(), GetExceptionInformation())){printf("在except内部\n");}printf("主函式结束\n"); } 与Windows异常处理机制的关係[编辑]Windows作业系统(自Windows95起) , 对每个用户执行绪 , 都设立一个异常处理帧鍊表来处理异常事件 。该鍊表的每个异常处理帧由两个成员组成 , 分别是鍊表上一项地址、当前异常处理器地址 , 组成了结构_EXCEPTION_REGISTRATION_RECORD 。异常处理器是指一个处理异常的回调函式(callback function) 。执行绪信息块(thread information block)的开始处(即FS:[0]指向的记忆体 , FS是CPU的一个段暂存器)保存了异常处理帧鍊表的表头项的地址 。程式执行遇到异常事件而中断时 , 作业系统的RtlDispatchException函式会从FS:[0]指向的鍊表表头依次调用每个节点包含异常处理回调函式 , 直到某个异常处理回调函式的返回值为0表示已经处理该异常 , 该执行绪可以恢复执行 。鍊表最末一项是作业系统在装入执行绪时设定的指向kernel32!UnhandledExceptionFilter函式 , 该函式总是向用户显示“Application error”对话框 。上述异常处理器程式及鍊表 , 是由用户程式自己安装的 。鍊表各节点保存在程式调用栈(call stack)上 。Windows异常处理机制支持嵌套异常的处理 , 即在执行异常处理回调函式时再次发生异常 。这种情况下仍遵照普通异常处理机制 , 作业系统RtlDispatchException函式再入处理新出现的嵌套的异常 。嵌套的异常的处理函式得到的DispatcherContext参数值即为在执行时发生了新异常的异常帧的地址 。各种程式语言基于上述Windows异常处理机制 , 设计了各自的异常处理语句控制结构 。Microsoft扩展了C语言语法 , 设计了结构化异常处理的try-except与try-finally语句 。一个函式的所有在函式的的try-except与try-finally形成了一个基于包含(enclosing)关係的森林 (数据结构) 。一个函式内如果有__try语句 , 则在函式的入口与结尾处 , 编译器插入了EH_prolog与EH_epilog代码 , 把函式内所有在try块中被保护的代码包了起来 。EH_prolog在调用栈上创建一个_EXCEPTION_REGISTRATION_RECORD , 作为异常处理鍊表的新的表头 , 其中包含了Visual C++ 的运行时库msvcrt.dll的__except_handler4函式地址 。在函式块的结尾处 , EH_epilog把这项_EXCEPTION_REGISTRATION_RECORD从鍊表头移除 , 恢复其原来的表头 。__except语句中的过滤表达式 , 由挂在鍊表中的异常处理回调函式MSVCR100D!__except_handler4来调用执行 , 返回值即为过滤表达式的求值结果 。实际上 , 编译器实现结构化异常时 , 把鍊表每项的数据结构由2个成员扩展为5个成员 , 即在高地址方向追加了一个scopetable_entries类型结构体数组的指针、一个整型项表示执行点位于当前函式的哪个try块中、一个保存暂存器EBP的整数项 。此后(低地址方向)紧接着是一个指向EXCEPTION_POINTERS结构的指针(前述的内在函式GetExceptionInformation即返回这个指针值) 。异常发生时 , 作业系统的异常处理机制的ntdll!RtlDispatchException函式会从FS:[0]指向的鍊表表头依次调用异常帧鍊表的每个节点所包含的异常处理回调函式MSVCR100D!__except_handler4 , 根据该回调函式的返回值来确定异常是否已经被处理 , 可以根据异常上下文(Exception context)恢複线程的执行 。__except_handler4回调函式实际上只是调用了MSVCR100D!__except_handler4_common函式 。__except_handler4_common函式是实际的workhorse , 负责在当前异常帧所在的函式中查找那个try-except语句能够处理该异常(即过滤表达式的结果为1)。如果不存在这样的try-except块 , __except_handler4_common函式返回ExceptionContinueExecution(值0) , 由RtlDispatchException继续访问异常帧鍊表的下一个节点 。如果找到了能处理当前异常的try-except块 , __except_handler4_common函式首先调用全局展开函式_EH4_GlobalUnwind , 把从异常帧鍊表表头所在的函式 , 直到能处理当前异常的函式的下一层函式 , 都做栈展开(unwinding);然后 , 对能处理当前异常的函式 , 从包含执行点的最记忆体__try语句 , 直到能处理异常的try-except块 , 调用局部展开函式_EH4_LocalUnwind;再执行能处理异常的try-except块的异常处理代码;最后 , 继续执行try-except之后的其他代码 。全局展开 , 是由_EH4_GlobalUnwind调用ntdll!RtlUnwind , RtlUnwind遍历访问异常帧鍊表 , 把从表头帧到目标帧(不含)的所有异常处理回调函式用异常码(STATUS_UNWIND 即0C0000027H)、异常标誌(EXCEPTION_UNWINDING即值2)调用 。异常处理回调函式根据当前的异常码与异常标誌 , 对当前异常帧所在函式 , 从包含了产生异常的执行点的最内层__try语句开始 , 直至函式内的最外层try块 , 依次调用try-finally的清理用途代码 。这一步实际上是调用_EH4_LocalUnwind函式来完成 。局部展开 , 由_EH4_LocalUnwind函式实现 , 是从包含了产生异常的执行点的最内层__try语句开始 , 按着代码的包含关係向外直至目标try-except语句为止 , 依次调用try-finally的清理用途代码 。