setjmp/longjmp的使用

在看上古代码时发现了setjmp/longjmp函数的使用,由于没见过就查了一下资料,发现这玩意及其花活,特记录一下。

setjmp/longjmp 的设计目的是非局部跳转(non-local goto),主要用于错误恢复和异常处理机制。让我详细讲解它的正确用法和典型场景。

核心设计目的

┌─────────────────────────────────────────┐
│  setjmp/longjmp 设计哲学                 │
│                                         │
│  "在同一条调用栈上进行远距离跳转"          │
│  不是线程/协程切换,而是"超级return"       │
└─────────────────────────────────────────┘

典型应用场景

1. 错误恢复(C语言中的"异常处理")

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

jmp_buf error_recovery;

void risky_operation(int x) {
    if (x < 0) {
        printf("Error: negative value %d\n", x);
        longjmp(error_recovery, 1);  // 跳转到安全点
    }
    if (x == 0) {
        printf("Error: division by zero risk\n");
        longjmp(error_recovery, 2);  // 不同错误码
    }
    printf("Success: processing %d\n", x);
}

int main() {
    int error_code = setjmp(error_recovery);
    
    if (error_code == 0) {
        // 首次进入,设置跳转点
        printf("Starting operations...\n");
        risky_operation(10);   // 正常
        risky_operation(-5);   // 触发错误,跳回
    } else {
        // 从 longjmp 跳回
        printf("Recovered from error code: %d\n", error_code);
    }
    
    printf("Program continues safely\n");
    return 0;
}

输出

Starting operations...
Success: processing 10
Error: negative value -5
Recovered from error code: 1
Program continues safely

关键点risky_operation 的栈帧在 longjmp 时被正确销毁,控制流回到 main,栈状态一致。

2. 多层调用栈的回滚

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

jmp_buf checkpoint;

void level_3() {
    printf("In level_3, about to fail\n");
    longjmp(checkpoint, 42);  // 直接跳回main,跳过level_2和level_1
}

void level_2() {
    printf("In level_2\n");
    level_3();
    printf("This won't print\n");
}

void level_1() {
    printf("In level_1\n");
    level_2();
    printf("This won't print either\n");
}

int main() {
    int result = setjmp(checkpoint);
    
    if (result == 0) {
        printf("Starting deep call...\n");
        level_1();
    } else {
        printf("Recovered from deep stack with code: %d\n", result);
    }
    
    return 0;
}

对比普通return:需要每层都检查返回值并手动传递,代码臃肿。