45-超越 goto 的跳转 longjmp

来源:互联网 发布:阿里云dns教程 编辑:程序博客网 时间:2024/06/05 17:20

不明所以的同学可能觉得本篇和信号这一专题关系不大,实际上,本篇是为 sigsetjmp 和 siglongjmp 函数作铺垫。但是在这讲这两函数前,先学习更简单的 setjmp 函数和 longjmp 函数。

1. 回忆 goto

回忆 C 语言中的 goto 语句,它所起到的作用就直接从一条语句跳转到另一条语句。这种程序往往破坏了程序的结构,所以专家们都不提倡使用 goto 语句,不过这个我们并不关心,讲 goto 是为了引出 longjmp 函数。

大家都知道 goto 语句只能在函数内跳转,并不能跨越函数进行跳转,像下面这样。

void func() {hello:  printf("hello world\n");  goto hello;}

跨越函数?不能,下面这种用法不可行。

void func1() {hello:  printf("hello world\n");  func2();}void func2() {  goto hello;}

不过,脑洞大开的程序员们设计了一个称之为 longjmp 的函数,它可以帮我们搞定这种 nb 的跳转。

2. longjmp

goto 语句有与之配套的一个标号,longjmp 也不例外,只不过 longjmp 配套的标号仍然是一个函数——setjmp.

如果修改前面的程序,大概是这样的:

jmp_buf hello;void func1() {  setjmp(hello);  printf("hello world\n");  func2();}void func2() {  longjmp(hello);}

不要试图编译上面的程序,这已经被简化了。

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;}
  • 编译和运行
$ gcc longjmp.c -o longjmp$ ./longjmp

接下来,会在屏幕每 2 秒打印一个 hello world.

hello worldhello worldhello worldhello world...

4. 为什么会这样?

我知道你心中有一万头草泥马奔腾而过,不过,搞懂原理后,写出这样的函数对你来说简直就是渣。原理有点复杂,请参考下一篇博文(勿抛砖)。

5. 函数原型

5.1 setjmp

int setjmp(jmp_buf env)

当第一次程序显式调用 setjmp 时,它的返回值是 0. 此后通过 longjmp 跳转到 setjmp 这个位置时,setjmp 的返回值是 longjmp 函数的第二个参数的值。

setjmp 的参数 env 必须是一个全局变量,它用来保存当前程序运行环境(一系列寄存器及栈帧里的关键值)。此后 longjmp 需要依据此 env 来跳转到 setjmp 的位置。

实际上 jmp_buf 是一个固定大小的数组(比如大小为 16 ?)。

5.2 longjmp

void longjmp(jmp_buf env, int val);

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();  }}
  • 编译和运行
$ gcc jmp.c -o jmp$ ./jmp

输入 100 和 200 后的结果:

100 // 输入 100hello! res = 1200 // 输入 200hello! res = 2

6. 总结

  • 学会 setjmp 和 longjmp 的用法
  • 思考这是如何做到的?(下一篇讲解原理)

练习:请尝试自己实现一个 setjmp 和 longjmp 函数。(提示:1. 需要使用汇编;2. 由于 gcc 不支持编写 naked 函数,请使用 Visual Studio 编程器)。

0 0
原创粉丝点击