Skip to content

C 标准库 - <setjmp.h>

概述

<setjmp.h> 头文件提供了非本地跳转(non-local goto)的功能,允许程序从一个函数跳转到另一个函数中预先设置的位置。这种机制通常用于错误处理和异常处理。

jmp_buf 类型

c
typedef struct {
} jmp_buf;

jmp_buf 是一个数组类型,用于保存跳转点的环境信息。

函数

setjmp()

c
int setjmp(jmp_buf env);

保存当前执行环境到 jmp_buf 中。

  • 第一次调用时返回 0
  • 通过 longjmp() 跳转回来时返回非零值(由 longjmp() 指定)
c
#include <stdio.h>
#include <setjmp.h>

jmp_buf env;

void function() {
    printf("在函数中\n");
    longjmp(env, 1);
    printf("这行不会执行\n");
}

int main() {
    int ret = setjmp(env);
    
    if (ret == 0) {
        printf("第一次调用 setjmp\n");
        function();
    } else {
        printf("从 longjmp 跳转回来,返回值: %d\n", ret);
    }
    
    return 0;
}

longjmp()

c
void longjmp(jmp_buf env, int val);

恢复之前保存的执行环境,跳转到对应的 setjmp() 调用处。

  • env - 之前由 setjmp() 保存的环境
  • val - 传递给 setjmp() 的返回值(0 会被转换为 1)
c
#include <stdio.h>
#include <setjmp.h>

jmp_buf env;

void function(int error) {
    if (error) {
        printf("发生错误,跳转回主函数\n");
        longjmp(env, error);
    }
    printf("函数正常执行\n");
}

int main() {
    int ret = setjmp(env);
    
    if (ret == 0) {
        printf("第一次调用 setjmp\n");
        function(0);
        function(1);
    } else {
        printf("从 longjmp 跳转回来,错误代码: %d\n", ret);
    }
    
    return 0;
}

实际应用示例

1. 错误处理

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

jmp_buf error_env;

typedef enum {
    ERROR_NONE = 0,
    ERROR_FILE_NOT_FOUND,
    ERROR_MEMORY,
    ERROR_INVALID_DATA
} ErrorCode;

void read_file(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) {
        printf("文件打开失败: %s\n", filename);
        longjmp(error_env, ERROR_FILE_NOT_FOUND);
    }
    
    printf("文件读取成功\n");
    fclose(file);
}

void allocate_memory(size_t size) {
    void* ptr = malloc(size);
    if (ptr == NULL) {
        printf("内存分配失败\n");
        longjmp(error_env, ERROR_MEMORY);
    }
    
    printf("内存分配成功\n");
    free(ptr);
}

void process_data(int value) {
    if (value < 0) {
        printf("无效的数据值: %d\n", value);
        longjmp(error_env, ERROR_INVALID_DATA);
    }
    
    printf("数据处理成功: %d\n", value);
}

int main() {
    int error = setjmp(error_env);
    
    if (error == ERROR_NONE) {
        printf("开始处理\n");
        read_file("test.txt");
        allocate_memory(1024);
        process_data(100);
        process_data(-1);
    } else {
        switch (error) {
            case ERROR_FILE_NOT_FOUND:
                printf("错误: 文件未找到\n");
                break;
            case ERROR_MEMORY:
                printf("错误: 内存不足\n");
                break;
            case ERROR_INVALID_DATA:
                printf("错误: 无效的数据\n");
                break;
        }
    }
    
    return 0;
}

2. 深度递归退出

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

jmp_buf recursion_env;

int recursive_search(int arr[], int size, int target, int depth) {
    printf("递归深度: %d\n", depth);
    
    if (depth > 10) {
        printf("递归深度过大,退出\n");
        longjmp(recursion_env, 1);
    }
    
    if (size == 0) {
        return 0;
    }
    
    if (arr[0] == target) {
        printf("找到目标: %d\n", target);
        longjmp(recursion_env, 1);
    }
    
    return recursive_search(arr + 1, size - 1, target, depth + 1);
}

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    if (setjmp(recursion_env) == 0) {
        printf("开始搜索\n");
        recursive_search(arr, size, 5, 0);
        printf("搜索完成\n");
    } else {
        printf("从递归中跳转出来\n");
    }
    
    return 0;
}

3. 状态机

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

jmp_buf state_env;

typedef enum {
    STATE_INIT,
    STATE_RUNNING,
    STATE_PAUSED,
    STATE_STOPPED
} State;

State current_state = STATE_INIT;

void transition_to(State new_state) {
    printf("状态转换: %d -> %d\n", current_state, new_state);
    current_state = new_state;
}

void handle_init() {
    printf("处理初始化状态\n");
    transition_to(STATE_RUNNING);
}

void handle_running() {
    printf("处理运行状态\n");
    transition_to(STATE_PAUSED);
}

void handle_paused() {
    printf("处理暂停状态\n");
    transition_to(STATE_STOPPED);
}

void handle_stopped() {
    printf("处理停止状态\n");
    longjmp(state_env, 1);
}

int main() {
    if (setjmp(state_env) == 0) {
        while (1) {
            switch (current_state) {
                case STATE_INIT:
                    handle_init();
                    break;
                case STATE_RUNNING:
                    handle_running();
                    break;
                case STATE_PAUSED:
                    handle_paused();
                    break;
                case STATE_STOPPED:
                    handle_stopped();
                    break;
            }
        }
    } else {
        printf("状态机结束\n");
    }
    
    return 0;
}

4. 资源清理

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

jmp_buf cleanup_env;

FILE* file = NULL;
void* buffer = NULL;

void cleanup() {
    if (file != NULL) {
        fclose(file);
        file = NULL;
        printf("文件已关闭\n");
    }
    
    if (buffer != NULL) {
        free(buffer);
        buffer = NULL;
        printf("内存已释放\n");
    }
}

void process() {
    file = fopen("test.txt", "w");
    if (file == NULL) {
        printf("文件打开失败\n");
        longjmp(cleanup_env, 1);
    }
    
    buffer = malloc(1024);
    if (buffer == NULL) {
        printf("内存分配失败\n");
        longjmp(cleanup_env, 1);
    }
    
    printf("处理成功\n");
}

int main() {
    if (setjmp(cleanup_env) == 0) {
        process();
    } else {
        printf("发生错误,清理资源\n");
    }
    
    cleanup();
    
    return 0;
}

5. 异常处理模拟

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

jmp_buf exception_env;

typedef enum {
    EXCEPTION_NONE = 0,
    EXCEPTION_DIVISION_BY_ZERO,
    EXCEPTION_NULL_POINTER,
    EXCEPTION_OUT_OF_RANGE
} Exception;

const char* exception_messages[] = {
    "无异常",
    "除零错误",
    "空指针错误",
    "超出范围错误"
};

void throw(Exception ex) {
    printf("抛出异常: %s\n", exception_messages[ex]);
    longjmp(exception_env, ex);
}

double divide(double a, double b) {
    if (b == 0.0) {
        throw(EXCEPTION_DIVISION_BY_ZERO);
    }
    return a / b;
}

void process_pointer(int* ptr) {
    if (ptr == NULL) {
        throw(EXCEPTION_NULL_POINTER);
    }
    *ptr = 100;
}

void check_range(int value, int min, int max) {
    if (value < min || value > max) {
        throw(EXCEPTION_OUT_OF_RANGE);
    }
}

int main() {
    Exception ex;
    
    if ((ex = setjmp(exception_env)) == EXCEPTION_NONE) {
        printf("开始处理\n");
        
        double result = divide(10.0, 2.0);
        printf("10.0 / 2.0 = %.2f\n", result);
        
        result = divide(10.0, 0.0);
        printf("这行不会执行\n");
        
    } else {
        printf("捕获异常: %s\n", exception_messages[ex]);
    }
    
    if ((ex = setjmp(exception_env)) == EXCEPTION_NONE) {
        printf("\n开始处理指针\n");
        
        int value;
        process_pointer(&value);
        printf("指针处理成功: %d\n", value);
        
        process_pointer(NULL);
        printf("这行不会执行\n");
        
    } else {
        printf("捕获异常: %s\n", exception_messages[ex]);
    }
    
    return 0;
}

6. 多级错误处理

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

jmp_buf level1_env;
jmp_buf level2_env;
jmp_buf level3_env;

void level3() {
    printf("进入第 3 级\n");
    
    if (setjmp(level3_env) == 0) {
        printf("第 3 级处理中...\n");
        longjmp(level3_env, 1);
    } else {
        printf("第 3 级错误处理\n");
        longjmp(level2_env, 1);
    }
}

void level2() {
    printf("进入第 2 级\n");
    
    if (setjmp(level2_env) == 0) {
        printf("第 2 级处理中...\n");
        level3();
    } else {
        printf("第 2 级错误处理\n");
        longjmp(level1_env, 1);
    }
}

void level1() {
    printf("进入第 1 级\n");
    
    if (setjmp(level1_env) == 0) {
        printf("第 1 级处理中...\n");
        level2();
    } else {
        printf("第 1 级错误处理\n");
    }
}

int main() {
    level1();
    printf("回到主函数\n");
    return 0;
}

7. 超时处理

c
#include <stdio.h>
#include <setjmp.h>
#include <time.h>
#include <unistd.h>

jmp_buf timeout_env;

void timeout_handler() {
    printf("超时!\n");
    longjmp(timeout_env, 1);
}

void long_operation() {
    printf("开始长时间操作...\n");
    
    for (int i = 0; i < 10; i++) {
        printf("进度: %d/10\n", i + 1);
        sleep(1);
        
        if (i == 5) {
            printf("操作被中断\n");
            longjmp(timeout_env, 1);
        }
    }
    
    printf("操作完成\n");
}

int main() {
    if (setjmp(timeout_env) == 0) {
        printf("开始执行\n");
        long_operation();
    } else {
        printf("操作超时或被中断\n");
    }
    
    return 0;
}

注意事项

1. 跳转范围

longjmp() 只能跳转到当前活动栈帧中的 setjmp() 调用点。

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

jmp_buf env;

void outer_function() {
    if (setjmp(env) == 0) {
        printf("outer_function: setjmp\n");
        inner_function();
    } else {
        printf("outer_function: longjmp 返回\n");
    }
}

void inner_function() {
    printf("inner_function: 调用 longjmp\n");
    longjmp(env, 1);
}

int main() {
    outer_function();
    return 0;
}

2. 变量值

longjmp() 跳转后,自动变量的值可能不确定,除非声明为 volatile

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

jmp_buf env;

void function() {
    int x = 10;
    volatile int y = 20;
    
    if (setjmp(env) == 0) {
        printf("第一次: x = %d, y = %d\n", x, y);
        x = 30;
        y = 40;
        longjmp(env, 1);
    } else {
        printf("第二次: x = %d, y = %d\n", x, y);
    }
}

int main() {
    function();
    return 0;
}

3. 资源管理

使用 setjmp/longjmp 时要确保资源正确释放。

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

jmp_buf env;

void* resource = NULL;

void cleanup() {
    if (resource != NULL) {
        free(resource);
        resource = NULL;
    }
}

void allocate() {
    resource = malloc(1024);
    if (resource == NULL) {
        longjmp(env, 1);
    }
}

int main() {
    if (setjmp(env) == 0) {
        allocate();
        printf("资源分配成功\n");
    } else {
        printf("资源分配失败\n");
    }
    
    cleanup();
    return 0;
}

4. 返回值

longjmp() 的返回值不能为 0,如果传递 0 会被转换为 1。

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

jmp_buf env;

void function() {
    longjmp(env, 0);
}

int main() {
    int ret = setjmp(env);
    printf("返回值: %d\n", ret);
    
    if (ret == 0) {
        function();
    }
    
    return 0;
}

总结

<setjmp.h> 提供的非本地跳转机制是一种强大的错误处理和异常处理工具,但需要谨慎使用:

  1. 适用场景 - 错误处理、异常处理、深度递归退出
  2. 注意事项 - 变量值、资源管理、跳转范围
  3. 替代方案 - 考虑使用传统的错误处理机制

记住:

  • setjmp/longjmp 会绕过正常的函数返回机制
  • 使用 volatile 关键字保护重要的变量
  • 确保资源正确释放
  • 不要过度使用,优先使用传统的错误处理