不明所以的同学可能觉得本篇和信号这一专题关系不大,实际上,本篇是为 sigsetjmp 和 siglongjmp 函数作铺垫。但是在这讲这两函数前,先学习更简单的 setjmp 函数和 longjmp 函数。
1. 回忆 goto
回忆 C 语言中的 goto 语句,它所起到的作用就直接从一条语句跳转到另一条语句。这种程序往往破坏了程序的结构,所以专家们都不提倡使用 goto 语句,不过这个我们并不关心,讲 goto 是为了引出 longjmp 函数。
大家都知道 goto 语句只能在函数内跳转,并不能跨越函数进行跳转,像下面这样。
void func() {
hello:
printf("hello world\n");
goto hello;
}
- 1
- 2
- 3
- 4
- 5
跨越函数?不能,下面这种用法不可行。
void func1() {
hello:
printf("hello world\n");
func2();
}
void func2() {
goto hello;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
不过,脑洞大开的程序员们设计了一个称之为 longjmp 的函数,它可以帮我们搞定这种 nb 的跳转。
2. longjmp
goto 语句有与之配套的一个标号,longjmp 也不例外,只不过 longjmp 配套的标号仍然是一个函数——setjmp.
如果修改前面的程序,大概是这样的:
jmp_buf hello;
void func1() {
setjmp(hello);
printf("hello world\n");
func2();
}
void func2() {
longjmp(hello);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
不要试图编译上面的程序,这已经被简化了。
3. 牛刀小试
先把上面的代码稍微修改修改就可以编译运行了:
- 代码
// longjmp.c
#include <unistd.h>
#include <setjmp.h>
#include <stdio.h>
jmp_buf hello; // 设置标号
void func2() {
longjmp(hello, 1);
}
void func1() {
setjmp(hello);
printf("hello world\n");
sleep(2); // 防止刷屏了
func2(); // 准备跳转
}
int main() {
func1();
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 编译和运行
$ gcc longjmp.c -o longjmp
$ ./longjmp
- 1
- 2
接下来,会在屏幕每 2 秒打印一个 hello world.
hello world
hello world
hello world
hello world
...
- 1
- 2
- 3
- 4
- 5
4. 为什么会这样?
我知道你心中有一万头草泥马奔腾而过,不过,搞懂原理后,写出这样的函数对你来说简直就是渣。原理有点复杂,请参考下一篇博文(勿抛砖)。
5. 函数原型
5.1 setjmp
int setjmp(jmp_buf env)
- 1
当第一次程序显式调用 setjmp 时,它的返回值是 0. 此后通过 longjmp 跳转到 setjmp 这个位置时,setjmp 的返回值是 longjmp 函数的第二个参数的值。
setjmp 的参数 env 必须是一个全局变量,它用来保存当前程序运行环境(一系列寄存器及栈帧里的关键值)。此后 longjmp 需要依据此 env 来跳转到 setjmp 的位置。
实际上 jmp_buf 是一个固定大小的数组(比如大小为 16 ?)。
5.2 longjmp
void longjmp(jmp_buf env, int val);
- 1
longjmp 第一个参数就是通过 setjmp 函数初始化后的值,第二个参数将通过 setjmp 返回值返回。
5.3 最后一个例子
下面这段程序,从终端读数据。如果你输入 100,longjmp 传递参数 1 并跳转到 setjmp 的位置,同时 setjmp 会返回 1. 如果你输入 200,longjmp 传递参数 2 并跳转到 setjmp 的位置,同时 setjmp 将返回 2.
- 代码
// jmp.c
#include <setjmp.h>
#include <stdio.h>
jmp_buf jmpbuf;
void doSomething() {
int n = 0;
scanf("%d", &n);
if (n == 100) {
longjmp(jmpbuf, 1);
}
if (n == 200) {
longjmp(jmpbuf, 2);
}
}
int main() {
int res = 0;
if ((res = setjmp(jmpbuf)) != 0) {
printf("hello! res = %d\n", res);
}
while(1) {
doSomething();
}
}
- 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
- 编译和运行
$ gcc jmp.c -o jmp
$ ./jmp
- 1
- 2
输入 100 和 200 后的结果:
100 // 输入 100
hello! res = 1
200 // 输入 200
hello! res = 2
- 1
- 2
- 3
- 4
6. 总结
- 学会 setjmp 和 longjmp 的用法
- 思考这是如何做到的?(下一篇讲解原理)
练习:请尝试自己实现一个 setjmp 和 longjmp 函数。(提示:1. 需要使用汇编;2. 由于 gcc 不支持编写 naked 函数,请使用 Visual Studio 编程器)。