c中的setjump和longjump

概念

goto可能会导致程序逻辑的混乱,并且非常容易导致程序栈帧的混乱,造成不可预知的后果。

setjump和longjump用于代替goto。由于操作系统调用会有很“深”的函数调用,当某些操作完成后,系统调用函数想要快速返回用户层函数而不是一层层地返回,所以这里经常用setjmp/longjmp。

另外笔者实践发现,某些时候递归函数使用setump/longjmp也能有很神奇的用处。

  • 使用setjmp/longjmp宏要包含setjmp.h
    1
    int setjmp(jmp_buf env);
  • env是jmp_buf类型的变量,用于保存setjmp时的环境
  • 第一次调用setjmp必返回0,之后依据longjmp返回
1
void longjmp(jmp_buf env,int ret_val);

(void是我猜的)

  • env 之前setjmp保存的环境,跳转到保存的那一行
  • ret_val 跳转到setjmp时返回值

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <setjmp.h>

jmp_buf buf;
void second(){
printf("second\n");
longjmp(buf,1); // setjmp返回1
}
void first(){
second();
printf("first\n"); // 执行不到这一行
}
int main() {
if(!setjmp(buf)){ // 第一次返回0,所以执行first,第二次由于longjmp,返回1,执行else
first();
}else{
printf("main\n");
}

}

输出

1
2
second
main

底层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

buf:
.zero 200
.LC0:
.string "second"
second():
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:.LC0
call puts
mov esi, 1
mov edi, OFFSET FLAT:buf
call longjmp
.LC1:
.string "first"
first():
push rbp
mov rbp, rsp
call second()
mov edi, OFFSET FLAT:.LC1
call puts
nop
pop rbp
ret
.LC2:
.string "main"
main:
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:buf
call _setjmp
test eax, eax
sete al
test al, al
je .L5
call first()
jmp .L6
.L5:
mov edi, OFFSET FLAT:.LC2
call puts
.L6:
mov eax, 0
pop rbp
ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef struct _JUMP_BUFFER
{
unsigned __int64 Frame;
unsigned __int64 Rbx;
unsigned __int64 Rsp;
unsigned __int64 Rbp;
unsigned __int64 Rsi;
unsigned __int64 Rdi;
unsigned __int64 R12;
unsigned __int64 R13;
unsigned __int64 R14;
unsigned __int64 R15;
unsigned __int64 Rip;
unsigned long MxCsr;
unsigned short FpCsr;
unsigned short Spare;

SETJMP_FLOAT128 Xmm6;
SETJMP_FLOAT128 Xmm7;
SETJMP_FLOAT128 Xmm8;
SETJMP_FLOAT128 Xmm9;
SETJMP_FLOAT128 Xmm10;
SETJMP_FLOAT128 Xmm11;
SETJMP_FLOAT128 Xmm12;
SETJMP_FLOAT128 Xmm13;
SETJMP_FLOAT128 Xmm14;
SETJMP_FLOAT128 Xmm15;
} _JUMP_BUFFER;

可以看到buffer内保存了所有寄存器和相关信息

参考资料

[1] C 库宏 - setjmp()