现在的位置: 首页 > 综合 > 正文

C语言中一种更优雅的异常处理机制 – setjmp/longjmp

2014年01月22日 ⁄ 综合 ⁄ 共 3344字 ⁄ 字号 评论关闭

转自:http://blog.chinaunix.net/u/22711/showart_445098.html

实际上goto语句是面向过程与面向结构化程序语言中,进行异常处理编程的最原始的支持形式。后来为了更好地、更方便地支持异常处理编程机制,使得程序员在C语言开发的程序中,能写出更高效、更友善的带有异常处理机制的代码模块来。于是,C语言中出现了一种更优雅的异常处理机制,那就是
setjmp()函数与longjmp()函数。


  实际上,这种异常处理的机制不是C语言中自身的一部分,而是在C标准库中实现的两个非常有技巧的库函数,也许大多数C程序员朋友们对它都很熟悉,而且,通过使用setjmp()函数与longjmp()函数组合后,而提供的对程序的异常处理机制,以被广泛运用到许多C语言开发的库系统中,如jpg解析库,加密解密库等等。

  也许C语言中的这种异常处理机制,较goto语句相比较,它才是真正意义上的、概念上比较彻底的,一种异常处理机制。作风一向比较严谨、喜欢刨根问底的主人公阿愚当然不会放
弃对这种异常处理机制进行全面而深入的研究。下面一起来看看。

setjmp函数有何作用?

  前面刚说了,setjmp是C标准库中提供的一个函数,它的作用是保存程序当前运行的一些状态。它的函数原型如下:

int setjmp( jmp_buf env );

  这是MSDN中对它的评论,如下:

  setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。当 setjmp和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”("non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块之中;或者程序中不采用正常的返回(return)语句,或函数的正常调用等方法,而使程序能被恢复到先前的一个调用例程(也即函数)中。

  对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且当前的程序控制流,会因此而返回到先前调用setjmp时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量。

  setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,请使用C++提供的异常处理机制。

  好了,现在已经对setjmp有了很感性的了解,暂且不做过多评论,接着往下看longjmp函数。

longjmp函数有何作用?

  同样,longjmp也是C标准库中提供的一个函数,它的作用是用于恢复程序执行的堆栈环境,它的函数原型如下:

void longjmp( jmp_buf env, int value );

  这是MSDN中对它的评论,如下:

  longjmp函数用于恢复先前程序中调用的setjmp函数时所保存的堆栈环境。setjmp和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”("non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块,或者不采用正常的返回(return)语句,或函数的正常调用等方法,使程序能被恢复到先前的一个调用例程(也即函数)中。

  对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且因此当前的程序控制流,会返回到先前调用setjmp时的执行点。此时,value参数值会被setjmp函数所返回,程序继续得以执行。并且,在接下来的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量;而寄存器类型的变量将不可预料。setjmp函数返回的值必须是非零值,如果longjmp传送的value参数值为0,那么实际上被setjmp返回的值是1。

  在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料。

  在使用longjmp时,请遵守以下规则或限制:
  · 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
  · 不要使用longjmp函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用_fpreset函数,来首先初始化浮点数包后,它是可以通过longjmp来实现从中断处理例程中返回。
  · 在C++程序中,小心对setjmp和longjmp的使用,应为setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。
把setjmp和longjmp组合起来,原来它这么厉害!
////////////////////////////////////////////////////////////////////////
#include 
#include 
jmp_buf buf;

void banana()
{
printf("in banana\n");
longjmp(buf,1);
printf("You will never see this, because i longjmped\n");
}
int main()
{
if(setjmp(buf))
printf("back in main\n");
else
{
printf("first time in through\n");
banana();
}
return 0;
}

/*
输出:
first time in through
in banana
back in main
*/

////////////////////////////////////////////////////////////////////////

不用OS,俺也能实现多任务切换。算是个“开裆裤”吧

#include
jmp_buf jumper0,jumper1,jumper2,jumper3;
void Task0()
{
static int n=0;
if(setjmp(jumper0)>0)
{
while(1)
{
if(setjmp(jumper0)==0) longjump(jumper1,1); //任务切换
n++; //一段任务代码
if(setjmp(jumper0)==0) longjump(jumper1,1); //任务切换
n++; //一段任务代码
}
}

}
void Task1()
{
static int n=0;
if(setjmp(jumper1)>0)
{
while(1)
{
if(setjmp(jumper1)==0) longjump(jumper2,1);
n++;
if(setjmp(jumper1)==0) longjump(jumper2,1);
n++;
}
}

}
void Task2()
{
static int n=0;
if(setjmp(jumper2)>0)
{
while(1)
{
if(setjmp(jumper2)==0) longjump(jumper3,1);
n++;
if(setjmp(jumper2)==0) longjump(jumper3,1);
n++;
}
}

}
void Task3()
{
static int n=0;
if(setjmp(jumper3)>0)
{
while(1)
{
if(setjmp(jumper3)==0) longjump(jumper0,1);
n++;
if(setjmp(jumper3)==0) longjump(jumper0,1);
n++;
}
}

}
void InitTask()
{
Task0();
Task1();
Task2();
Task3();
}
main()
{
InitTask();
longjmp(jumper0,1)
}

//以上代码在keil c中调试通过
//非占先式任务切换
//任务内变量必须是静态的,子程序不用
//任务内不要用寄存器变量

抱歉!评论已关闭.