一、原理
C语言中包含头文件 <setjump.h> 即可实现跨函数跳转,通常用于异常处理,在运行代码出现异常时可以自动跳转到调用setjump函数的位置。
1、实验
#include "setjmp.h"
jmp_buf g_jmp_buf;
int div__(int a,int b)
{
if(b==0)
{
longjmp(g_jmp_buf,1);
}
return a/b;
}
int main(void)
{
void init (void);
init();
int a=10;
int b=0;
if(setjmp(g_jmp_buf)==0)
{
printf("start calc %d/%d\r\n",a,b);
printf("%d/%d=%d\r\n",a,b,div__(a,b));
}
else
{
printf("something wrong\r\n");
}
}
输出为:
可见,当b==0时产生了异常,程序从div__()函数跳转回setjmp()函数的位置输出了"something wrong",而不是计算结果。
setjmp和longjmp函数的第一个参数是jmp_buf,这个变量用于存储上下文,setjmp.h中的描述为:
/* size as defined in the ATEPCS /
/ an array type suitable for holding the data /
/ needed to restore a calling environment. */
2、猜想
猜测实现方式是在setjmp函数中保存当前的上下文,也就是cpu的寄存器值,然后在调用longjmp函数时跳转到setjmp函数中再恢复上下文,这与多线程的实现方法类似,只是多线程是把上下文保存到栈中,setjmp可以保存到全局变量或堆中,只需要保证jmp_buf的生命周期在调用setjm和longjmp期间是有效的即可。
二、实现
以下代码在Keil5环境下可以编译通过。
定义jmp_buf数据类型:
typedef u32 my_jmp_buf[48];
定义setjmp和longjmp函数:
AREA |.text|, CODE, READONLY, ALIGN=2
THUMB
REQUIRE8
PRESERVE8
my_setjmp PROC
EXPORT my_setjmp
STR r0,[r0]
STR r1,[r0,#4]
STR r2,[r0,#8]
STR r3,[r0,#12]
STR r4,[r0,#16]
STR r5,[r0,#20]
STR r6,[r0,#24]
STR r7,[r0,#28]
STR r8,[r0,#32]
STR r9,[r0,#36]
STR r10,[r0,#40]
STR r11,[r0,#44]
STR r12,[r0,#48]
STR r13,[r0,#52]
STR r14,[r0,#56]
;存入PC指针
LDR r1,=return_from_longjmp
STR r1,[r0,#60]
MOV r0,#0
BX r14
;从longjmp返回时会从这里开始执行
return_from_longjmp
STR r1,[r0]
LDR r1,[r0,#4]
LDR r2,[r0,#8]
LDR r3,[r0,#12]
LDR r4,[r0,#16]
LDR r5,[r0,#20]
LDR r6,[r0,#24]
LDR r7,[r0,#28]
LDR r8,[r0,#32]
LDR r9,[r0,#36]
LDR r10,[r0,#40]
LDR r11,[r0,#44]
LDR r12,[r0,#48]
LDR r13,[r0,#52]
LDR r14,[r0,#56]
LDR r0,[r0]
BX r14
ENDP
my_longjmp PROC
EXPORT my_longjmp
LDR r2,[r0,#60]
BX r2
ENDP
ALIGN 4
END
三、调用
typedef u32 my_jmp_buf[48];
extern int my_setjmp(my_jmp_buf);
extern void my_longjmp(my_jmp_buf,int);
my_jmp_buf g_jmp_buf;
int div__(int a,int b)
{
if(b==0)
{
my_longjmp(g_jmp_buf,1);
}
return a/b;
}
int main(void)
{
void init (void);
init();
int a=10;
int b=0;
if(my_setjmp(g_jmp_buf)==0)
{
printf("start calc %d/%d\r\n",a,b);
printf("%d/%d=%d\r\n",a,b,div__(a,b));
}
else
{
printf("something wrong\r\n");
}
}
输出为:
可以看到现象与使用库函数的现象相同。
四、总结
标准库中规定longjmp不能使setjmp返回0:
After longjmp is completed, program execution continues as if
the corresponding call to setjmp had just returned the value
specified by val. The longjmp function cannot cause setjmp to
return the value 0; if val is 0, setjmp returns the value 1.
而在下写的函数my_longjmp是可以使my_setjmp返回0的,这会造成一个奇怪的无限循环,例如:
typedef u32 my_jmp_buf[48];
extern int my_setjmp(my_jmp_buf);
extern void my_longjmp(my_jmp_buf,int);
my_jmp_buf g_jmp_buf;
int div__(int a,int b)
{
if(b==0)
{
my_longjmp(g_jmp_buf,0);
}
return a/b;
}
int main(void)
{
void init (void);
init();
int a=10;
int b=0;
if(my_setjmp(g_jmp_buf)==0)
{
printf("start calc %d/%d\r\n",a,b);
printf("%d/%d=%d\r\n",a,b,div__(a,b));
}
else
{
printf("something wrong\r\n");
}
}
输出为: