异常处理和非本地跳转
异常处理
异常处理机制允许程序中独立开发的部分能够在运行时才出现的问题进行通信并做出相应的处理。异常使得我们能够将问题的检测与解决过程分离开来。程序的一部分负责检测问题的出现,然后解决该问题的任务传递给程序的另一部分。
C++中,可以使用throw关键词抛出一个异常。当执行一个throw时,跟在throw后面的语句将不再被执行。相反,程序的控制权从throw转移到与之匹配的catch模块。即程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch子句。当throw出现在一个try语句块内时,检查与该try块关联的catch子句。如果找到了匹配的catch,就使用该catch处理异常。如果这一步没找到匹配的catch且该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句。如果还是找不到匹配的catch,则退出当前函数,在调用当前函数的外层函数中继续寻找。这个过程称为栈展开。
在栈展开的过程中,位于调用链上的语句块可能会提前退出,局部对象会进行销毁,如果局部对象是类类型,会自动调用其析构函数。下面是一个例子:
#include <iostream>
using namespace std;
class Base{
public:
Base(){
cout << "Base()" << endl;
}
~Base(){
cout << "~Base()" << endl;
}
};
int divide(int a, int b) noexcept(false){
Base base;
if (b == 0){
cout << "b = 0"<<endl; /* automatically call objects destructor int the stack */
throw b;
}
cout << "b != 0" << endl;
return a / b;
}
int main(int argc, char const *argv[]){
try{
divide(10,0);
}catch(int e){ /* int exceptions */
cout<<"divide by zero"<<endl;
}catch( ... ){ /* other exceptions */
throw; /* didn't handle this exception */
}
return 0;
}
值得注意的是,C++的异常处理和Java是不同的。C++的异常处理只能处理throw中抛出的异常,而不能处理语句的异常,如try语句中有一个除0操作,仍然会导致程序退出,而在Java中不会。
非本地跳转
C语言提供一种用户级别的异常控制流形式,称为非本地跳转,它将控制字节从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。非本地跳转时通过setjmp和longjmp函数来提供的。
#include<setjmp.h>
int setjmp(jmp_buf env); /* 直接调用setjmp返回0,从longjmp返回非0 */
int longjmp(jmp_buf env, int retval);
setjmp函数在env缓冲区中保存当前的调用环境,以供后面的longjmp使用,并返回0。调用环境包括程序计数器、栈指针和通用目的寄存器。longjmp函数从env缓冲区恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用中返回。然后setjmp返回,并带有非零的返回值retval。下面是具体的例子:
#include<stdio.h>
#include<stdlib.h>
#include<setjmp.h>
int divide(int a, int b, jmp_buf jmpBuf){
char *s = (char *)malloc(sizeof(char) * 10);
if(b == 0){
free(s);
printf("b == 0\n");
longjmp(jmpBuf,1);
}
printf("b != 0\n");
return a/b;
}
int main(int argc, char const *argv[])
{
jmp_buf jmpBuf;
if( (setjmp(jmpBuf)) !=0 ){
printf("divide by zero\n");
goto end;
}
divide(10,0,jmpBuf);
end:
return 0;
}