您的位置:首页 > 编程语言 > C语言/C++

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

2012-03-09 11:13 369 查看
转自: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中调试通过

//非占先式任务切换

//任务内变量必须是静态的,子程序不用

//任务内不要用寄存器变量
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: