C 语言 setjmp/longjmp
更新时间:2026-08-26。本文是
languages/c/主题专家层文档。C 没有异常机制,但setjmp/longjmp提供了一种"非本地跳转"——从深层函数直接跳回很久之前保存的位置,就像"时光倒流"。这是 C 模拟异常处理、实现错误传播的底层工具,也是协程的雏形。
本文要回答的问题
setjmp为什么"调用一次、返回两次"?longjmp跳转时,栈发生了什么?局部变量还在吗?- 用 setjmp/longjmp 模拟 try-catch 要避开哪些坑?
一、非本地跳转是什么
普通 goto 只能在函数内跳,longjmp 能跨函数、跨多层调用栈跳:
c
#include <setjmp.h>
jmp_buf env;
void inner(int depth) {
if (depth == 0) {
longjmp(env, 42); // 从深层直接跳回 setjmp 处
}
inner(depth - 1);
}
int main(void) {
int r = setjmp(env); // ① 保存当前上下文
if (r == 0) {
inner(5); // ② 深入 5 层后 longjmp
} else {
printf("跳回,返回值 %d\n", r);
}
}实测:
text
第一次 setjmp 返回 0,开始递归
longjmp 触发,跳回 setjmp 处
longjmp 跳回,setjmp 第二次返回 42二、两次返回机制
setjmp 的奇妙之处——调用一次,返回两次:
| 返回 | 时机 | 返回值 |
|---|---|---|
| 第一次 | setjmp 被调用时 | 0 |
| 第二次 | longjmp 跳回时 | longjmp 携带的值(非 0) |
所以代码里用 if (r == 0) 区分"正常流程"和"longjmp 跳回"。
三、longjmp 时栈发生了什么

longjmp 会恢复 setjmp 时保存的寄存器(rsp/rbp/rip 等),栈指针直接弹回,中间的调用栈帧全部作废——那些函数里的局部变量全部失效。
四、setjmp/longjmp 的坑
| 坑 | 说明 |
|---|---|
| 局部变量失效 | longjmp 后,跳过的栈帧里的非 volatile 局部变量值不确定 |
| 资源泄漏 | 中间栈帧里 malloc 的、打开的文件,longjmp 后没人释放 |
| 不可移植 | 是 C 标准的一部分,但语义细节各平台有差异 |
| volatile 限制 | setjmp 到 longjmp 之间被修改的变量,若 longjmp 后要读,必须是 volatile |
c
jmp_buf env;
void f(void) {
int x = 1; // 非 volatile
volatile int y = 1; // volatile
x = 2; y = 2;
if (setjmp(env) == 0) longjmp(env, 1);
// longjmp 后:y 保证是 2,x 的值不确定!
printf("y=%d\n", y); // 安全
// printf("x=%d\n", x); // 危险,x 可能被恢复成 1
}五、用 longjmp 模拟异常
C 里常见的错误传播模式:
c
jmp_buf err_env;
#define TRY if (setjmp(err_env) == 0)
#define CATCH else
#define THROW(x) longjmp(err_env, x)
void risky(void) {
/* 出错时 */
THROW(1); // "抛出异常",跳到 CATCH
}
int main(void) {
TRY {
risky();
/* 正常路径 */
} CATCH {
/* 错误处理 */
printf("捕获错误\n");
}
}这是 C 里模拟 try-catch 的经典技巧(但注意上面的资源泄漏坑)。
六、与本站主线衔接
- 栈帧与寄存器:setjmp 保存的是哪些寄存器,见 内存布局与 ABI。
- 协程:setjmp/longjmp 是用户态协程的基础,见 协程原理。
- 信号:longjmp 与信号处理的交互,见 信号处理。
- volatile:setjmp 与 volatile 的关系,见 volatile 与 restrict。
一句话总结
setjmp 保存上下文(返回 0)、longjmp 恢复上下文跳回(返回非 0),实现"跨函数非本地跳转";它是 C 模拟异常的底层工具,但跳过栈帧的局部变量会失效、资源会泄漏,跨 setjmp 的变量要 volatile——功能强但坑多,慎用。