<setjmp.h> 提供跨函數棧幀跳轉能力:setjmp 保存當前執行環境,longjmp 恢復環境並跳轉回 setjmp 處。類似異常處理的底層機制,但不調用析構函數/清理代碼,屬「非受控跳轉」。
setjmp/longjmp 之間傳遞env,返回 0longjmp 的 val 參數 (非 0)if (setjmp(env)) 以外)env 保存的環境,跳轉到對應 setjmpval 成為 setjmp 的返回值val == 0 會被強制轉為 1setjmp 所在函數已返回,行為未定義 (棧已銷毀)
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
jmp_buf env_buf;
void deep_function(int level) {
printf("深度 %d: 進入\n", level);
if (level == 3) {
printf("深度 3: 觸發錯誤,長跳轉!\n");
longjmp(env_buf, 42); // 跳回 main 的 setjmp
}
deep_function(level + 1);
printf("深度 %d: 返回 (不會執行)\n", level);
}
int main() {
int ret = setjmp(env_buf);
if (ret == 0) {
printf("main: 設置跳轉點,調用深層函數\n");
deep_function(1);
} else {
printf("main: 從 longjmp 返回, 值=%d\n", ret);
}
return 0;
}
main: 設置跳轉點,調用深層函數 深度 1: 進入 深度 2: 進入 深度 3: 進入 深度 3: 觸發錯誤,長跳轉! main: 從 longjmp 返回, 值=42
注意:deep_function 的後續 printf 和 return 完全被跳過,棧幀直接展開。
| 場景 | 後果 | 標準分類 |
|---|---|---|
| longjmp 目標函數已返回 | 棧損壞、崩潰、靜默數據錯誤 | 未定義行為 |
| 跳過變長數組 (VLA) 作用域 | 棧指針錯誤 | 未定義行為 (C99) |
| 跳過 C++ 對象構造/析構 | 資源洩漏、雙重釋放、邏輯錯誤 | C++ 禁用,用異常 |
| 信號處理函數中 longjmp | 僅 async-signal-safe 函數安全 | POSIX 限制 |
| 多線程共享 jmp_buf | 競態條件、棧混亂 | 每線程獨立 env |
jmp_buf error_env;
int parse_config(const char *file) {
if (setjmp(error_env) != 0) return -1; // 錯誤返回點
FILE *fp = fopen(file, "r");
if (!fp) longjmp(error_env, 1);
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (parse_line(line) != 0) longjmp(error_env, 2);
}
fclose(fp);
return 0;
}
// 深層調用
int parse_line(char *line) {
if (strncmp(line, "ERROR", 5) == 0) longjmp(error_env, 3);
return 0;
}
typedef struct {
jmp_buf ctx;
char stack[8192];
void (*entry)(void);
} coroutine_t;
void co_yield(coroutine_t *from, coroutine_t *to) {
if (setjmp(from->ctx) == 0) longjmp(to->ctx, 1);
}
void co_entry(void *arg) {
coroutine_t *co = arg;
co->entry();
longjmp(co->ctx, 0); // 結束返回調度器
}
這是 ucontext/libco/protothreads 等協程庫的核心原理。
jmp_buf test_env;
#define ASSERT(cond) do { if(!(cond)) longjmp(test_env, __LINE__); } while(0)
void run_test(void (*test_fn)(void)) {
if (setjmp(test_env) == 0) {
test_fn();
printf("PASS\n");
} else {
printf("FAIL at line %d\n", (int)setjmp(test_env)); // 實際需保存值
}
}
#include <signal.h>
#include <setjmp.h>
jmp_buf sig_env;
volatile sig_atomic_t sig_caught = 0;
void sig_handler(int sig) {
sig_caught = sig;
longjmp(sig_env, 1); // 從阻塞系統調用跳出
}
int main() {
signal(SIGALRM, sig_handler);
alarm(5); // 5 秒超時
if (setjmp(sig_env) == 0) {
// 執行可能阻塞的操作
blocking_operation();
} else {
printf("操作超時或收到信號 %d\n", sig_caught);
}
return 0;
}
Result<T, E> 模式、errno、std::expected (C++23)co_await、co_yield、編譯器支持棧幀管理libunwind、setjmp/longjmp 封裝庫 (如 ckjmp)