【C语言】(21)非局部跳转库setjmp

129 阅读3分钟

setjmp 库提供了在 C 程序中进行非局部跳转的机制,它主要由两个函数组成:setjmplongjmp。这两个函数通常用于异常处理和程序控制流的改变,尤其在错误恢复过程中非常有用。这种机制允许程序从深层嵌套的函数调用中跳转回到一个预先指定的恢复点。

头文件

要使用 setjmplongjmp 函数,需要包含头文件 setjmp.h

#include <setjmp.h>

类型

  • jmp_buf: 这是一个类型,用于声明一个变量,该变量能够存储跳转环境信息,包括程序计数器、栈指针和寄存器的值等。jmp_buf 类型的变量通常作为 setjmplongjmp 函数的参数。

函数

setjmp

用于设置一个跳转点,保存当前的执行环境(包括栈帧信息、程序计数器等)到 jmp_buf 类型的变量中。如果直接从 setjmp 返回,它返回 0。如果是通过 longjmp 跳转回来,它返回一个非零值。

#include <setjmp.h>

int setjmp(jmp_buf env);
  • 参数: jmp_buf env 是一个特殊类型的数组,用于存储跳转点的环境信息,包括程序计数器、栈指针和部分寄存器的值。
  • 返回值: 当直接从 setjmp 调用返回时,它返回 0。当通过 longjmp 函数跳转回 setjmp 的位置时,它返回一个非零值,这个非零值是通过 longjmp 传递的。

longjmp

longjmp 函数用于跳转到之前由 setjmp 设置的跳转点。longjmp 的原型如下:

#include <setjmp.h>

void longjmp(jmp_buf env, int val);
  • 参数:
    • jmp_buf env: 同 setjmp 中的 env 参数,表示跳转点的环境信息。
    • int val: 传递给 setjmp 的返回值,用于指示跳转的原因。为了确保 setjmp 正确地接收到非零值,如果 val 为 0,longjmp 实际上会返回 1。

例1

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

jmp_buf buf;

void second() {
    printf("second\n");         // 输出
    longjmp(buf, 1);            // 跳回 setjmp 的调用位置
}

void first() {
    second();
    printf("first\n");          // 不会执行
}

int main() {   
    if (!setjmp(buf)) {
        first();                // 进入此行前,setjmp 返回 0
    } else {                    // 当 longjmp 跳转回,setjmp 返回 1,因此进入此行
        printf("main\n");       // 输出
    }

    return 0;
}

输出结果为:

second
main

例2:错误处理

下面是 setjmplongjmp 在错误处理中的一个示例用法:

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

jmp_buf jumpBuffer;

void throwError() {
    longjmp(jumpBuffer, 1); // 触发跳转
}

int main() {
    if (setjmp(jumpBuffer) == 0) {
        printf("Normal flow\n");
        throwError(); // 模拟错误
    } else {
        // 错误处理代码
        printf("An error occurred\n");
    }
    return 0;
}

注意事项

  • 使用 setjmplongjmp 时需要特别注意资源管理,因为它们会跳过正常的堆栈解除和构造过程。这意味着如果你在跳转之前分配了资源(例如动态内存、打开的文件等),可能需要在跳转之后手动释放这些资源,以避免资源泄露。
  • 由于 longjmp 跳转的非局部性,可能会导致代码难以理解和维护,因此在必要时才使用这种机制。
  • 跨越函数调用进行跳转时,应确保跳转不会导致局部变量的作用域被非正常终止,避免引入潜在的bug。