非本地跳转 (NonLocal Jump)
ddatsh
函数内进行跳转,可以用goto
但从一个函数内跳转到另一个函数的某处,goto 不能完成
setjmp 和 longjmp 用来完成这种类型的分支跳转,在处理异常上非常有用
setjmp/longjmp
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int retval);
#include <stdio.h>
#include <setjmp.h>
int E_FOO = 1;
int E_BAR = 2;
int E_UNKOWN = -1;
static jmp_buf env;
void foo(void) {
// longjmp(env, E_FOO); // 相当于 throw(E_FOO)
}
void bar(void) {
longjmp(env, E_BAR); // 相当于 throw(E_BAR)
}
int main()
{
int ret = 0;
int rc;
rc = setjmp(env);
if(rc == 0) { // 相当于 try block
foo();
bar();
printf("run success\n");
} else if(rc == E_FOO) { // 相当于 catch(E_FOO)
printf("error happened in foo()\n");
ret = E_FOO;
} else if(rc == E_BAR) { // 相当于 catch(E_BAR)
printf("error happened in bar()\n");
ret = E_BAR;
} else { // 相当于 catch(Exception)
printf("error happened\n");
ret = E_UNKOWN;
}
}
setjmp
保存当前的环境(即程序的状态)到数据结构 (jmp_buf
),随后可被 longjmp
“跳转"回setjmp
所保存的程序执行状态
longjmp
完成后,程序从对应的setjmp
调用处继续执行,如同setjmp
调用刚刚完成。longjmp
retval传0,setjmp
的返回值为1;否则,setjmp
的返回值为retval
即setjmp
函数相当于设置一个程序运行时点,第一次返回 0 表示设置运行时点成功,第二次返回由longjmp
,第二次的返回值是longjmp
函数的第二个参数值
longjmp
函数永不返回,它只是迫使setjmp
函数第二次返回,并设置其返回值
setjmp
函数和longjmp
函数配合能达到类似于 Java 中的try
、catch
、throw
的效果
惯用法:
if( setjmp(x) ){
/* handle longjmp(x) */
}
switch(setjmp(env)):
case 0: //default
//...
case 1: //exception 1
//...
case 2: //exception 2
//...
//...
java & c
public class Main {
static class BadException extends Exception {
BadException(String msg) {
super(msg);
}
}
static double divide(double to, double by) throws BadException {
if (by == 0)
throw new BadException("Cannot / 0");
return to / by;
}
static void f() {
try {
divide(2, 0);
//...
} catch (BadException e) {
System.out.println(e.getMessage());
}
System.out.println("done");
}
public static void main(String[] args) {
f();
}
}
#include <stdio.h>
#include<setjmp.h>
static jmp_buf env;
double divide(double to, double by) {
if (by == 0) {
longjmp(env, 1);
}
return to / by;
}
void f() {
if (setjmp(env) == 0) {
divide(2, 0);
}
else {
printf("Cannot / 0\n");
}
printf("done\n");
}
void main() {
f();
}
sigsetjmp/siglongjmp
POSIX setjmp
函数并不保存 signal(BSD 会保存 signal),如果需要连同 signal 一并保存的话,用另一组函数
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjump_buf env, int retval);
sigsetjmp
函数多了一个参数savesigs
,不为 0,则会将信号掩码保存到env
中,否则不保存
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
static sigjmp_buf env;
void handler(int sig) {
siglongjmp(env, 1); // return non-zero value
}
int main() {
signal(SIGINT, handler); // 重置Ctrl-C信号
int rc;
rc = sigsetjmp(env, 1); // 1: 保存信号掩码
if (rc == 0) {
printf("started\n");
}
else {
printf("\rrestarted\n");
}
int i = 0;
while (i++ < 10) {
sleep(1); // sleep 1 second
printf("processing...%d\n", i);
}
return 0;
}
输出类似
started
processing...1
processing...2
processing...3
restarted # 按下Ctrl-C
processing...1
processing...2
restarted # 按下Ctrl-C
processing...1
setjmp 也可以用来模拟 coroutine 。但正确的 coroutine 实现需要为每个 coroutine 配备一个独立的数据栈,setjmp 无法做到
setcontext 库 ,已经不是 C 语言的标准库了