结构化异常处理

结构化异常处理，是Windows操作系统上，Microsoft对C/C++程序语言做的语法扩展，用于处理异常事件的程序控制结构。
异常事件是打断程序正常执行流程的不在期望之中的硬件、软件事件。硬件异常是CPU抛出的如“除0”、数值溢出等；软件异常是操作系统与程序通过RaiseException语句抛出的异常。
Microsoft扩展了C语言的语法，用 try-except与try-finally语句来处理异常。异常处理程序可以释放已经获取的资源、显示出错信息与程序内部状态供调试、从错误中恢复、尝试重新执行出错的代码或者关闭程序等等。
一个__try语句不能既有__except，又有__finally。但try-except与try-finally语句可以嵌套使用。

try-finally语句

__try {
     // 受保护执行的代码
 }__finally {
     // 清理用途的代码}
__try复合语句中受保护的代码以任何方式执行结束后，不论__try复合语句是因为出现异常而非正常结束，还是没有出现异常而正常结束，__finally复合语句中的代码都会被执行。
如果__try复合语句中受保护的代码的执行没有出现异常，包括用goto语句或longjump系统函数跳出__try复合语句等情形，这时将执行__finally复合语句，然后执行try-finally语句之后的其他代码。
在__finally复合语句中使用内在函数AbnormalTermination判断是正常结束还是非正常结束__try复合语句。

执行顺序

如果__try复合语句中受保护的代码执行中出现了异常，首先按函数调用顺序从新向旧搜索所有包含了出现异常的执行点的try-except语句，执行每个except的过滤函数，直到某个try-except语句的过滤函数结果值为EXCEPTION_EXECUTE_HANDLER。这时，再从包含异常出现的执行点的最内层try-finally语句开始由内向外执行每个finally块中的代码，直至回退到那个过滤函数结果值为EXCEPTION_EXECUTE_HANDLER的try-except语句执行其except块，最后执行该try-except语句后面的其他代码。 
__leave关键字用在try-finally 语句的受保护节（try块）中，其效果是跳转到受保护节的结尾处。 与goto语句跳出受保护节不同，__leave语句更为有效，因为它不会导致堆栈展开。

例子

#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 NULL

   printf("开始主程序\n");
   __try{
      printf("进入外层的try\n");
      __try{
         printf("进入内层的try\n");
         int *p=0; // 空指针
         *p = 13;    // 导致访问冲突异常
      }__finally{
         printf("在finally内部。");
         printf(AbnormalTermination() ? "非正常终止\n" : "正常终止\n");
      }
   }__except(filter(GetExceptionCode(), GetExceptionInformation())){
      printf("在except内部\n");
   }
   printf("主函数结束\n");}
输出结果为：
开始主程序
进入外层的try
进入内层的try
在异常表达式中
接受处理访问违例异常
在finally内部。非正常终止
在except内部
主函数结束

与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的清理用途代码。

SAFESEH

为了防止篡改异常处理函数的地址，自Windows XP SP2起引入了检查异常处理函数的地址是否有效。但这仅用于x86。而x64或ARM已经把所有异常处理函数放在了PDATA节中；该节叫做“.pdata”或者Exception Directory,用于存放处理异常的信息。
链接器选项/SAFESEH[:NO]用于设置是否需要开启。
如果/SAFESEH被使用，链接器生成的可执行映像文件（image）包含它的安全的异常处理函数的清单。 
如果/SAFESEH未使用，链接器将检查如果所有的模块都兼容于安全的异常处理函数特性，则生成的可执行映像文件（image）包含它的安全的异常处理函数的清单；如果有模块不兼容于安全的异常处理函数特性，则生成的可执行映像文件（image）不包含安全的异常处理函数的清单。
如果/SAFESEH:NO被使用，链接器生成的可执行映像文件（image）不包含安全的异常处理函数的清单。