setjmp()和longjmp()函数

时间:2021-05-24 11:21:47

之前我们讲到了过程活动记录(AR),那么如何来操纵AR呢,一个可能的方法是,根据局部变量的地址进行推算,例如对于上面的a函数,执行a函数时的当前AR地址就是参数i的地址偏移8个字节,也就是 ((char*)&i) - 8。

然而,不同的C编译器,以及不同的硬件平台都会产生不同的AR结构布局,甚至在一些平台上,AR根本不会存放到Stack中(也可能放在寄存器里,这样运行速度更快一点)。所以这种方式操纵AR是不通用的。

为此,C语言通过库函数的方式提供了操纵AR的统一方法,那就是setjmp和longjmp函数。

(注意:goto语句不能跳出C语言当前的函数

1. 作用:

setjmp()和longjmp() 可以实现非局部控制转移即从一个函数到另外一个函数的跳转。

2. 函数原型:

int setjmp(jmp_buf j);
void longjmp(jmp_buf j, int i);

setjmp函数设置返回点,保存调用函数的栈环境与j中(相当于保护现场)。 l

ongjmp的作用是使用setjmp保存在j中的栈环境信息返回到setjmp的位置,也就是当执行longjmp时程序又回到setjmp处(相当于恢复现场)。

setjmp有两个作用:

1)保存调用函数的栈环境于j中,返回值为0

2)作为longjmp的返回目标地,返回值为longjmp的第二个参数i,使代码能够知道它是实际上是通过longjmp返回的

当然,当使用longjmp()时,j的内容被销毁。

3. jmp_buf数据类型

typedef struct __jmp_buf_tag jmp_buf[];

jmp_buf实际是一个数组,内存分配在栈空间中,作为参数传递时是一个指针(指向调用函数的栈帧)。

4. 具体实例

#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
void haha()
{
printf("in haha()\n");
longjmp(buf,);
printf("kaonima\n");
}
int tim=; int main()
{
if(setjmp(buf))
{
printf("back in main\n");
tim++;
}
else
{
printf("first time through\n");
haha();
}
if(tim<) longjmp(buf,);
return ;
}

运行结果是:

first time through
in haha()
back in main
back in main
back in main

6.异常处理

这里举一个使用这两个函数进行异常处理的例子

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h> jmp_buf jb; void f1()
{
printf("进入f1()\n");
if(/*正确执行*/){ }
else {
longjmp(jb,);
}
printf("退出f1()\n");
}
void f2()
{
printf("进入f2()\n");
if(/*正确执行*/) { }
else {
longjmp(jb, );
}
printf("退出f2()\n");
} int main()
{
int r = setjmp(jb);
if(r==){
f1();
f2();
}else if(r==){
printf("处理错误1\n");
exit();
}else if(r==){
printf("处理错误2\n");
exit();
}
return ;
}

当然完整的异常处理远比这里的代码要复杂,需要考虑异常的嵌套等,这里仅仅给出最简单的思路。

注:不要在C++中使用setjmp和longjmp

C++为异常处理提供了直接支持。除非极特殊需要,不要再重新实现自己的异常机制,尤其需要说明的是,简单的调用setjmp/longjmp有可能带来问题。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h> class MyClass
{
public:
MyClass(){ printf("MyClass::MyClass()\n");}
~MyClass(){ printf("MyClass::~MyClass()\n");}
};
jmp_buf jb; void f1()
{
MyClass obj;
printf("进入f1()\n");
if(/*正确执行*/){ }
else {
longjmp(jb,);
}
printf("退出f1()\n");
}
void f2()
{
printf("进入f2()\n");
if(/*正确执行*/) { }
else {
longjmp(jb, );
}
printf("退出f2()\n");
} int main()
{
int r = setjmp(jb);
if(r==){
f1();
f2();
}else if(r==){
printf("处理错误1\n");
exit();
}else if(r==){
printf("处理错误2\n");
exit();
}
return ;
}

g++编译,程序输出:

MyClass::MyClass()
进入f1()
处理错误1

vc++编译,程序输出:

MyClass::MyClass()
进入f1()
MyClass::~MyClass()
处理错误1

longjmp()跳转前局部对象可能并不会析构(g++),也可能析构(VC++),C++标准对此并无明确要求。这种依赖于具体编译器版本的代码是应该避免的。

而C++本身的throw关键字,却能严格保证局部对象构造和析构的成对调用。

参考:

https://blog.csdn.net/smstong/article/details/50728022

https://blog.csdn.net/c1194758555/article/details/52780068

https://blog.csdn.net/qq_33656136/article/details/52732970

C专家编程 6.8节