C 标准库 setjmp.h

  • Post category:C

C 标准库 setjmp.h 提供了一种在程序中进行非局部跳转的机制,即 longjmp() 函数。这种机制可以用于实现异常处理等功能。本文将详细讲解 setjmp.h 的使用方法。

setjmp 和 longjmp 函数

setjmp 和 longjmp 函数是 setjmp.h 提供的两个主要函数,下面分别简单介绍它们的作用。

setjmp 函数

setjmp 函数用于在当前程序执行位置设置一个用于非局部跳转的上下文。用法如下:

#include <setjmp.h>

int setjmp(jmp_buf env);
  • env:一个 jmp_buf 类型的变量,用于存储上下文信息。

setjmp 函数的返回值为 0,表示正常返回;非 0,则表示从 longjmp 函数跳转回来。注意:setjmp 函数返回的是整型,不同返回值表示不同的跳转来源,应该预先定义一些宏来表示跳转的来源(以便后面进行判断)。

longjmp 函数

longjmp 函数用于在程序任意位置进行非局部跳转。用法如下:

#include <setjmp.h>

void longjmp(jmp_buf env, int val);
  • env:一个 jmp_buf 类型的变量,用于指定跳转的上下文。
  • val:要返回的值。

longjmp 函数不返回,而是直接跳转到 setjmp 函数调用的位置,并返回指定的值,在这个过程中,程序会跳过中间所有的函数调用,回到 setjmp 函数的位置处。

使用攻略

接下来介绍 setjmp.h 的使用攻略,并提供两个示例程序。

示例 1:非局部跳转

下面是一个基本的使用示例,用于演示如何进行非局部跳转。

#include <stdio.h>
#include <setjmp.h>

#define FIRST_JUMP 1
#define SECOND_JUMP 2

jmp_buf buf;            // 用于存储上下文

void my_function(void)
{
    printf("my_function start\n");
    longjmp(buf, SECOND_JUMP);   // 非局部跳转,返回到调用 setjmp 的位置
    printf("my_function end\n"); // 不会被执行
}

int main(void)
{
    int jmp_val;    

    if ((jmp_val = setjmp(buf)) != 0) {
        // longjmp 返回,执行到这里
        printf("setjmp returned %d\n", jmp_val);
    } else {
        // 第一次调用 setjmp
        printf("starting here...\n");
        my_function();
        printf("never reach this point\n");
    }

    return 0;
}

运行结果:

starting here...
my_function start
setjmp returned 2

这个程序运行的流程可以用下图表示:

+---------------+
|  main() start |
+---------------+
         |
         |-----+#1 setjmp(buf)
         |
  +------|-----+#2----> +----------------+
  |      |       |      |  my_function()  |
  |      |       |      +----------------+
  |      |       |            |
  |      +-------+            |
  |              |-----+#3 longjmp(buf, SECOND_JUMP)
  |              |      |
  |      +--------      |
  |      |       |       |
  +------|-------|-------+
         |       |
         |       |-----+#4 setjmp(buf)
         |       |
  +------|-------|-------+
  |      |       |      | 
  |      |       |      |
  |      +-----< #5 <----+
  |              |      
  +--------------+

如上所述,程序从 main 函数开始,调用 setjmp 函数,获取上下文信息,并将返回值保存到 jmp_val 变量中。如果返回值为 0,说明是第一次调用 setjmp 函数,那么程序会执行 my_function 函数,然后在 my_function 函数中调用 longjmp 函数,返回到 setjmp 调用的位置,这时返回值为 SECOND_JUMP,进入 if 块,输出 “setjmp returned 2″;如果返回值为非 0,则说明是 longjmp 函数返回,这时也会进入 if 块,输出 “setjmp returned 2″。

示例 2:使用 setjmp 和 longjmp 进行错误处理

下面是一个实际应用的示例,用于演示如何使用 setjmp 和 longjmp 函数进行错误处理,比如说在编译器中遇到错误就可以利用这种技术实现类似异常处理的功能。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf escape;  // 用于存储当前状态的上下文

void do_division(int x, int y) {
    if (y == 0) {
        longjmp(escape, 1);  // 检测到错误
    }
    printf("x/y = %.2f\n", (float)x / (float)y);
}

int main(int argc, char *argv[]) {
    int x = 10;
    int y = 0;
    int ret = 0;

    if ((ret = setjmp(escape)) != 0) {
          printf("an error occurred during division\n");
          exit(1);
    }

    do_division(x, y);

    return 0;
}

运行结果:

an error occurred during division

这个程序运行的流程可以用下图表示:

 +------------------+
 |   main() start   |
 +------------------+
          |
          |-----+#1 setjmp(escape)
          |
   +------|-----+#2----> +----------------+
   |      |       |      |  do_division() |
   |      |       |      +----------------+
   |      |       |            |
   |      | -----+ <-----------+
   |      |      |  y == 0, calls longjmp(escape, 1)
   +------| -----+
          |
          |      +-------------------+
          |      | ret = setjmp(escape)
          |      |  ret == 0, continue
          +------+  
            |
            +------- exit(1)

如上所述,程序从 main 函数开始,调用 setjmp 函数,获取上下文信息,并将返回值保存到 ret 变量中。如果返回值为 0,说明是第一次调用 setjmp 函数,那么程序会执行 do_division 函数,在 do_division 函数中通过判断 y 是否为 0,来检测是否发生了错误,如果检测到错误,就调用 longjmp 函数,跳回到主函数中,并传递参数值 1,表示发生了错误,进入 if 块,输出错误信息并退出程序(exit(1));如果返回值为非 0,则说明是 longjmp 函数返回,这时会直接进入 if 块,输出错误信息并退出程序。