跳转
除了 if、循环和 goto 这类结构化控制流,C 标准库还提供了非局部跳转能力:setjmp 与 longjmp。它们定义在 <setjmp.h>,可在深层调用栈中快速回到某个恢复点。
1. 基本语义
setjmp(env) 会保存当前执行上下文,并返回 0;后续若调用 longjmp(env, value),程序会跳回该 setjmp 位置,并让 setjmp 的返回值变为 value(若 value 为 0,则返回 1)。
#include <setjmp.h>
#include <stdio.h>
static jmp_buf env;
void deep_call(void) {
longjmp(env, 42);
}
int main(void) {
int code = setjmp(env);
if (code == 0) {
deep_call();
} else {
printf("recovered: %d\n", code);
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
2. 适用场景
它适合表达“统一失败回收点”这类机制,例如解释器、解析器或受限环境中的错误回退。若普通 return 就能清晰表达控制流,应优先使用普通 return,因为可读性更高。
3. 关键约束
longjmp 只能跳回仍然有效的调用栈上下文;若目标函数已经返回,再跳回去就是未定义行为。另外,setjmp 与自动存储期对象的可见值之间存在细节约束,涉及优化与寄存器分配时尤其要谨慎。
4. 局部对象可见值提示
在 setjmp 返回后,如果某些自动存储期对象在中间路径被修改过,且未使用 volatile,再次通过 longjmp 回到该点时,这些对象的可见值可能不符合直觉。涉及关键状态时,应显式使用更稳妥的状态承载方式,避免依赖编译器优化细节。
5. 使用建议
把 setjmp/longjmp 限定在极小范围,并把资源释放策略写成可复核模板。非局部跳转不是异常系统的完全替代,它是底层工具,适合精确控制,不适合泛化滥用。
6. 与普通错误路径的分层
更稳妥的组织方式通常是:常规错误仍走 return 路径,只有“跨多层调用快速回退”的场景才使用 setjmp/longjmp。把这两类路径分层后,控制流会更容易维护,也更便于做资源释放核对。
7. 恢复点应尽量单一
在一个模块里同时放置多个 setjmp 恢复点会显著提高路径复杂度。更清晰的做法通常是:由上层入口设立一个主要恢复点,下层只负责上报失败并让控制流回到该点。恢复点越少,资源回收与状态复位就越容易验证。
8. 与线程模型的边界
jmp_buf 保存的是当前执行上下文,不应跨线程混用。每个线程都应维护自己的恢复点与错误收束路径,避免把一个线程保存的上下文交给另一个线程跳转。把非局部跳转严格限制在线程内部,是确保语义稳定的基础约束。