45-超越 goto 的跳转 longjmp

不明所以的同学可能觉得本篇和信号这一专题关系不大,实际上,本篇是为 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 world
hello world
hello world
hello 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 // 输入 100
hello! res = 1
200 // 输入 200
hello! res = 2

6. 总结

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

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

相关推荐
<p> <strong><span style="background-color:#FFFFFF;color:#E53333;font-size:24px;">本页面购买不发书!!!仅为视频课购买!!!</span></strong> </p> <p> <strong><span style="color:#E53333;font-size:18px;">请务必到</span></strong><a href="https://edu.csdn.net/bundled/detail/49?utm_source=banner"><strong><span style="color:#E53333;font-size:18px;">https://edu.csdn.net/bundled/detail/49</span></strong></a><strong><span style="color:#E53333;font-size:18px;">下单购买课+书。</span></strong> </p> <p> <span style="font-size:14px;">本页面,仅为观看视频页面,如需一并购买图书,请</span><span style="font-size:14px;">务必到</span><a href="https://edu.csdn.net/bundled/detail/49?utm_source=banner"><span style="font-size:14px;">https://edu.csdn.net/bundled/detail/49</span></a><span style="font-size:14px;">下单购买课程+图书!!!</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">疯狂Python精讲课程覆盖《疯狂Python讲义》全书的主体内容。</span> </p> <span style="font-size:14px;">内容包括Python基本数据类型、Python列表、元组和字典、流程控制、函数式编程、面向对象编程、文件读写、异常控制、数据库编程、并发编程与网络编程、数据可视化分析、Python爬虫等。</span><br /> <span style="font-size:14px;"> 全套课程从Python基础开始介绍,逐步步入当前就业热点。将会带着大家从Python基础语法开始学习,为每个知识点都提供对应的代码实操、代码练习,逐步过渡到文件IO、数据库编程、并发编程、网络编程、数据分 析和网络爬虫等内容,本课程会从小案例起,至爬虫、数据分析案例终、以Python知识体系作为内在逻辑,以Python案例作为学习方式,最终达到“知行合一”。</span><br />
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页