You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在setjmp()与longjmp()间传递超出int类型的数据?

用setjmp/longjmp实现简易try/catch异常系统的实践与踩坑指南

刚好我之前折腾过用setjmplongjmp手搓简易异常处理,踩了不少坑,来跟你详细唠唠这块的细节:

先搞懂C99对setjmp的严格限制

你提到的C99规范限制是实打实的,官方文档里明确规定:

严格C99规范不允许读取setjmp的结果并存入变量

为啥会有这个奇怪的限制?因为setjmp本质是个宏,不是普通函数——首次调用它会保存当前执行上下文并返回0,而当longjmp跳转回来时,它其实是“模拟”返回一个你传入的非0值。如果把它的返回值存到变量里,编译器的优化逻辑可能会破坏上下文恢复的正确性,直接导致行为未定义。

合法的用法是直接把setjmp的返回值用在条件判断里,比如:

if (setjmp(exception_buf) == 0) {
    // 正常执行逻辑(对应try块)
} else {
    // 异常处理逻辑(对应catch块)
}

而下面这种写法在严格C99里绝对是违规的:

int ret = setjmp(exception_buf); // 行为未定义!

实现简易try/catch的核心思路

我们可以用宏来封装这个逻辑,把setjmp的条件判断藏在宏里,完美规避直接存返回值的问题。比如定义TRYCATCHTHROW宏:

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

// 线程局部的异常上下文,用来传递自定义数据(多线程安全)
_Thread_local struct {
    int ex_type;       // 异常类型标识
    const char* msg;   // 异常描述信息
    void* data;        // 自定义数据指针(支持任意类型)
} current_exception;

jmp_buf exception_buf;

#define TRY if (setjmp(exception_buf) == 0) {
#define CATCH } else {
#define THROW(type, msg, data) do { \
    current_exception.ex_type = type; \
    current_exception.msg = msg; \
    current_exception.data = data; \
    longjmp(exception_buf, 1); \
} while(0)

解决不同类型数据的传递问题

正如你所说,longjmp只能传递一个int值,没法直接传字符串、结构体这些复杂类型。解决办法就是用上面的current_exception结构体:

  • 在调用THROW抛出异常前,先把要传递的异常类型、描述、自定义数据塞进这个上下文结构体
  • 跳转到CATCH块后,直接读取这个结构体里的内容就行

下面是一个完整的可运行示例:

// 定义自定义异常类型
#define EXCEPTION_DIV_BY_ZERO 1
#define EXCEPTION_OUT_OF_MEM 2
#define EXCEPTION_INVALID_ARG 3

void risky_calculation(int a, int b) {
    if (b == 0) {
        THROW(EXCEPTION_DIV_BY_ZERO, "Division by zero detected", NULL);
    }
    if (a < 0) {
        // 传递自定义数据(比如错误的参数值)
        int* invalid_arg = malloc(sizeof(int));
        *invalid_arg = a;
        THROW(EXCEPTION_INVALID_ARG, "Negative argument not allowed", invalid_arg);
    }
    int* big_buf = malloc(1024 * 1024 * 100);
    if (!big_buf) {
        THROW(EXCEPTION_OUT_OF_MEM, "Failed to allocate large buffer", NULL);
    }
    free(big_buf);
    printf("Calculation succeeded: %d/%d = %d\n", a, b, a/b);
}

int main() {
    TRY {
        printf("Running risky calculation...\n");
        risky_calculation(-5, 2);
    } CATCH {
        switch(current_exception.ex_type) {
            case EXCEPTION_DIV_BY_ZERO:
                printf("Caught error: %s\n", current_exception.msg);
                break;
            case EXCEPTION_OUT_OF_MEM:
                printf("Caught error: %s\n", current_exception.msg);
                break;
            case EXCEPTION_INVALID_ARG:
                printf("Caught error: %s (invalid value: %d)\n", 
                       current_exception.msg, *(int*)current_exception.data);
                free(current_exception.data); // 记得释放自定义数据
                break;
            default:
                printf("Caught unknown exception\n");
        }
    }
    return 0;
}

额外的注意事项

  • 线程安全:如果是多线程程序,一定要用线程局部存储(C11的_Thread_local,或者GCC的__thread扩展)存放异常上下文,否则多个线程会互相干扰
  • 资源清理:这个简易实现没有finally块,如果需要在异常发生时清理资源(比如关闭文件、释放内存),得手动在CATCH块里处理,或者再加一个FINALLY宏来封装
  • 编译器优化:编译时最好关闭一些激进的优化(比如-O0或者-fno-omit-frame-pointer),避免编译器破坏setjmp/longjmp的上下文恢复逻辑

内容的提问来源于stack exchange,提问作者Qqwy

火山引擎 最新活动