C语言学习:非局部跳转setjmp.h中的几个重要函数
简介:
在C语言中,我们知道在一个函数的内部是可以通过goto来跳转到我们实现声明标志的地方的,但是这种跳转往往是基于函数内部实现的,而C标准库为我们提供的setjmp中,提供了setjmp,longjmp等函数,就是为了实现非局部跳转和goto的功能的。也是C语言实现异常处理的一种有效方案。
提示:
博客:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041
setjmp中的函数介绍:
#include <setjmp.h>
int setjmp(jmp_buf env);
//setjmp()宏把当前状态信息保存到env中,供以后longjmp()恢复状态信息时使用。如果是直接调用setjmp(),那么返回值为0;如果是由于调用longjmp()而调用setjmp(),那么返回值非0。setjmp()只能在某些特定情况下调用,如在if语句、 switch语句及循环语句的条件测试部分以及一些简单的关系表达式中
void longjmp(jmp_buf env, int val);
//longjmp()用于恢复由最近一次调用setjmp()时保存到env的状态信息。当它执行完时,程序就象setjmp()刚刚执行完并返回非0值val那样继续执行。包含setjmp()宏调用的函数一定不能已经终止。所有可访问的对象的值都与调用longjmp()时相同,唯一的例外是,那些调用setjmp()宏的函数中的非volatile自动变量如果在调用setjmp()后有了改变,那么就变成未定义的。
从代码的角度来展示一下
/*
* ===========================================================================
*
* Filename: jmp.cpp
* Description:
* Version: 1.0
* Created: 2017年07月25日 23时27分11秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
//标准C语言库中的的非局部跳转
#include<setjmp.h>
using namespace::std;
static jmp_buf static_buf; //用来存放处理器的上下文,主要用于跳转
void jmp(){
//调用longjmp之后,会载入static_buf的处理器信息,第二个参数作为setjmp的返回值
cout << "before calling" <<endl;
longjmp(static_buf,12); //12是错误的返回值,用来做差异处理
cout << "after calling "<<endl; //此句是不会执行的
}
int main(int argc,char* argv[]){
int ret = 0;
//将处理器信息保存到stati_buf中来,并且返回0,相当于一个标记
if((ret = setjmp(static_buf)) == 0){
cout << "first calling" << endl;
jmp();
} else {//出现错误
if(ret == 12){
cout <<" error"<< endl;
}
}
return 0;
}
执行结果:
first calling
before calling
error
从上面可以看到,显示去进行注册,当longjmp发生的时候,这个函数会去第二次执行判断。这个时候走的就是else部分。
还有两个重要的函数siglongjmp和sigsetjmp:
在看这两个函数之前,可以看到这两个函数都是sig开头的,而在posix标准中,sig一般都是signal信号的意思,那这两个函数务必是于signal信号相关的。事实上也确实是。
先了解下什么是信号屏蔽字,信号屏蔽字就是进程中被阻塞的信号集, 这些信号不能发送给该进程, 它们在该进程中被”屏蔽”了. 后面我们会提到, 实际上它们是被阻塞了.也就是信号屏蔽了。
sigsetjmp和siglongjmp出现的意义是什么???在上述的sigjmp函数和longjmp函数中会有这样一个比较明显的弊端,就是当捕捉到一个信号,进入信号执行函数后,仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。会默认将此信号加入到信号屏蔽字中,这就会导致后来的这种信号对于此类信号中断该信号处理程序。而sigsetjmp和siglongjmp恰恰是为了解决这样一种场景的发生的
void siglongjmp(sigjmp_buf env, int val);
int sigsetjmp(sigjmp_buf env, int savesigs);
//如果是直接调用则返回0,如果是由于siglongjmp来调用,则返回的是非0.
如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字,当用siglongjmp时,如果带 非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
sigsetjmp()会保存目前堆栈环境,然后将目前的地址作一个记号,
而在程序其他地方调用siglongjmp()时便会直接跳到这个记号位置,然后还原堆栈,继续程序的执行。
参数env为用来保存目前堆栈环境,一般声明为全局变量
/*
* ===========================================================================
*
* Filename: sigjmp.cpp
* Description:
* Version: 1.0
* Created: 2017年07月27日 21时56分33秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<iostream>
#include<signal.h>
#include<setjmp.h>
using namespace::std;
static sigjmp_buf buf;
void sig_handler(int signo){
cout << "before jmp" <<endl;
siglongjmp(buf,10); //在收到信号的时候,调用siglongjmp,恢复堆栈信息
}
int main(int argc,char *argv[]){
if(signal(SIGUSR2,sig_handler) == SIG_ERR){ //注册信号处理函数
cout << "signal error"<< endl;
}
int ret;
ret = sigsetjmp(buf,10); //保存当前的堆栈环境,将目前的地址做一个记号
switch(ret){
case 0:
cout << "first jmp" << endl;
break;
case 10:
cout << "after jmp" << endl;
break;
default:
break;
}
while(1);
return 0;
}
这个程序中,每次恢复栈之后,如果继续kill -12 xxx这个进程的话,依旧会持续的收到,这是因为其从信号屏蔽集中已经恢复。
注意:
在信号处理函数中的非局部跳转,尽量使用的是siglongjmp和sigsetjmp函数。使从信号处理函数中恢复到主函数中去