setjmp()和longjmp()函数
~~~~~
与刺激的abort()和exit()相比,goto语句看起来比较适合处理异常的情况。不过,goto是本地的,只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点。
~~~~~
为了解决这个限制,C函数库提供了setjmp()和longjmp()函数。setjmp()进行非局部标号的设置,而longjmp()是实现跳转的功能,调转到设置的标号处setjmp()。
~~~~~
头文件**<setjmp.h>**申明了这些函数及同时所需的jmp_buf数据类型。
~~~~~
原理非常简单:
1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。也就是第一次调用setjmp(j),初始化成功了,返回0。
2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。在作为长跳转的目标而被调用时(不是第一次初始化的时候),若longjmp(j,r)函数中r是大于0的整数,则跳转到setjmp(j)会返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那么跳转到setjmp(j)后,setjmp(j)返回时1,不会返回0。记住,setjmp()是不会在longjmp(j,0)时返回0的。
~~~~~
通过有两类返回值,setjmp()让你知道它正在被怎么使用。当第一次设置j时,setjmp(j)如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。
实际代码测试
(1)测试1
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void test1(void)
{
longjmp(j, 1); /* jump to setjmp then do case 1 */
printf("this line should never appear\n");
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("Case 1\n");
break;
default:
break;
}
return 0;
}
gcc jmp1.c 编译, ./a.out运行后,结果如下:
setjmp(j)==0 mains: it is initialized
Case 1
~~~~~
main函数中,第一次执行setjmp(j),setjmp(j)返回0,进入case 0,打印setjmp(j)==0 mains: it is initialized信息;
~~~~~
之后运行test1()函数,在test()函数中运行 longjmp(j, 1);直接跳转到main中的setjmp(j),main中和test1()中的printf(“this line should never appear\n”);语句是永远也不会执行的。
~~~~~
跳转到main中的setjmp(j)后,setjmp(j)执行返回1,进行case 1 分支,打印Case 1,break跳出,程序结束。
(2)测试2
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void test1(void)
{
longjmp(j, 0);
printf("this line should never appear\n");
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("Case 1\n");
break;
case 2:
printf("Case 2\n");
break;
default:
break;
}
return 0;
}
将test1()中longjmp(j, 1);更改为longjmp(j, 0),编译运行,结果如下:
setjmp(j)==0 mains: it is initialized
Case 1
第一次执行setjmp(j),进行初始化,返回0;
之后执行longjmp(j, 0);跳转到setjmp(j)返回1,不会返回0。
即上面强调过的:
在作为长跳转的目标而被调用时(不是第一次初始化的时候),若longjmp(j,r)函数中r是大于0的整数,则跳转到setjmp(j)会返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那么跳转到setjmp(j)后,setjmp(j)返回时1,不会返回0。setjmp()是不会在longjmp(j,0)时返回0的。
(3)
#include <setjmp.h>
#include <stdio.h>
#include <signal.h>
jmp_buf j;
void signal_hander(int signer)
{
longjmp(j, 3); /*get intterrupt sign , jump to setjmp ,do case 3, jump out */
}
void test1(void)
{
longjmp(j, 2); /* jump to setjmp then do case 2 */
printf("this line should never appear\n");
}
void test2(void)
{
longjmp(j, 1); /* jump to setjmp then do case 1 */
printf("this line should never appear\n");
}
int main(void)
{
signal(SIGINT, &signal_hander);
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("jump to Case 2\n");
test1();
break;
case 2:
printf("jump to Case 1\n");
test2();
break;
case 3:
printf("interrupted then jump out\n");
break;
default:
break;
}
return 0;
}
编译,运行,结果如下:
如上,main函数中第一次执行setjmp(j)进行初始化后,进入case 0 分支,执行test1();
test1()中执行longjmp(j,2),调试到转case 2分支,执行test2();
test2()中执行longjmp(j,1),调试到转case 1分支,执行test1();
所以程序正常运行时在case 1 和case 2 分支之间无限循环。
main函数中设置signal(SIGINT, &signal_hander);可以捕获中断信号;当按下ctrl +C ,捕获到中断信号,执行 signal_hander()函数,调用longjmp(j,3)后跳转到case 3 ,打印interrupted then jump out后程序break跳出结束。
总结:
~~~~~
setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。也就是第一次调用setjmp(j),初始化成功了,返回0。
~~~~~
以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。在作为长跳转的目标而被调用时(不是第一次初始化的时候),若longjmp(j,r)函数中r是大于0的整数,则跳转到setjmp(j)会返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那么跳转到setjmp(j)后,setjmp(j)返回时1,不会返回0。setjmp()是不会在longjmp(j,0)时返回0的。