Appearance
C 标准库 - <setjmp.h>
概述
<setjmp.h> 头文件提供了非本地跳转(non-local goto)的功能,允许程序从一个函数跳转到另一个函数中预先设置的位置。这种机制通常用于错误处理和异常处理。
jmp_buf 类型
c
typedef struct {
} jmp_buf;jmp_buf 是一个数组类型,用于保存跳转点的环境信息。
函数
setjmp()
c
int setjmp(jmp_buf env);保存当前执行环境到 jmp_buf 中。
- 第一次调用时返回 0
- 通过
longjmp()跳转回来时返回非零值(由longjmp()指定)
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void function() {
printf("在函数中\n");
longjmp(env, 1);
printf("这行不会执行\n");
}
int main() {
int ret = setjmp(env);
if (ret == 0) {
printf("第一次调用 setjmp\n");
function();
} else {
printf("从 longjmp 跳转回来,返回值: %d\n", ret);
}
return 0;
}longjmp()
c
void longjmp(jmp_buf env, int val);恢复之前保存的执行环境,跳转到对应的 setjmp() 调用处。
env- 之前由setjmp()保存的环境val- 传递给setjmp()的返回值(0 会被转换为 1)
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void function(int error) {
if (error) {
printf("发生错误,跳转回主函数\n");
longjmp(env, error);
}
printf("函数正常执行\n");
}
int main() {
int ret = setjmp(env);
if (ret == 0) {
printf("第一次调用 setjmp\n");
function(0);
function(1);
} else {
printf("从 longjmp 跳转回来,错误代码: %d\n", ret);
}
return 0;
}实际应用示例
1. 错误处理
c
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
jmp_buf error_env;
typedef enum {
ERROR_NONE = 0,
ERROR_FILE_NOT_FOUND,
ERROR_MEMORY,
ERROR_INVALID_DATA
} ErrorCode;
void read_file(const char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("文件打开失败: %s\n", filename);
longjmp(error_env, ERROR_FILE_NOT_FOUND);
}
printf("文件读取成功\n");
fclose(file);
}
void allocate_memory(size_t size) {
void* ptr = malloc(size);
if (ptr == NULL) {
printf("内存分配失败\n");
longjmp(error_env, ERROR_MEMORY);
}
printf("内存分配成功\n");
free(ptr);
}
void process_data(int value) {
if (value < 0) {
printf("无效的数据值: %d\n", value);
longjmp(error_env, ERROR_INVALID_DATA);
}
printf("数据处理成功: %d\n", value);
}
int main() {
int error = setjmp(error_env);
if (error == ERROR_NONE) {
printf("开始处理\n");
read_file("test.txt");
allocate_memory(1024);
process_data(100);
process_data(-1);
} else {
switch (error) {
case ERROR_FILE_NOT_FOUND:
printf("错误: 文件未找到\n");
break;
case ERROR_MEMORY:
printf("错误: 内存不足\n");
break;
case ERROR_INVALID_DATA:
printf("错误: 无效的数据\n");
break;
}
}
return 0;
}2. 深度递归退出
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf recursion_env;
int recursive_search(int arr[], int size, int target, int depth) {
printf("递归深度: %d\n", depth);
if (depth > 10) {
printf("递归深度过大,退出\n");
longjmp(recursion_env, 1);
}
if (size == 0) {
return 0;
}
if (arr[0] == target) {
printf("找到目标: %d\n", target);
longjmp(recursion_env, 1);
}
return recursive_search(arr + 1, size - 1, target, depth + 1);
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int size = sizeof(arr) / sizeof(arr[0]);
if (setjmp(recursion_env) == 0) {
printf("开始搜索\n");
recursive_search(arr, size, 5, 0);
printf("搜索完成\n");
} else {
printf("从递归中跳转出来\n");
}
return 0;
}3. 状态机
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf state_env;
typedef enum {
STATE_INIT,
STATE_RUNNING,
STATE_PAUSED,
STATE_STOPPED
} State;
State current_state = STATE_INIT;
void transition_to(State new_state) {
printf("状态转换: %d -> %d\n", current_state, new_state);
current_state = new_state;
}
void handle_init() {
printf("处理初始化状态\n");
transition_to(STATE_RUNNING);
}
void handle_running() {
printf("处理运行状态\n");
transition_to(STATE_PAUSED);
}
void handle_paused() {
printf("处理暂停状态\n");
transition_to(STATE_STOPPED);
}
void handle_stopped() {
printf("处理停止状态\n");
longjmp(state_env, 1);
}
int main() {
if (setjmp(state_env) == 0) {
while (1) {
switch (current_state) {
case STATE_INIT:
handle_init();
break;
case STATE_RUNNING:
handle_running();
break;
case STATE_PAUSED:
handle_paused();
break;
case STATE_STOPPED:
handle_stopped();
break;
}
}
} else {
printf("状态机结束\n");
}
return 0;
}4. 资源清理
c
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
jmp_buf cleanup_env;
FILE* file = NULL;
void* buffer = NULL;
void cleanup() {
if (file != NULL) {
fclose(file);
file = NULL;
printf("文件已关闭\n");
}
if (buffer != NULL) {
free(buffer);
buffer = NULL;
printf("内存已释放\n");
}
}
void process() {
file = fopen("test.txt", "w");
if (file == NULL) {
printf("文件打开失败\n");
longjmp(cleanup_env, 1);
}
buffer = malloc(1024);
if (buffer == NULL) {
printf("内存分配失败\n");
longjmp(cleanup_env, 1);
}
printf("处理成功\n");
}
int main() {
if (setjmp(cleanup_env) == 0) {
process();
} else {
printf("发生错误,清理资源\n");
}
cleanup();
return 0;
}5. 异常处理模拟
c
#include <stdio.h>
#include <setjmp.h>
#include <string.h>
jmp_buf exception_env;
typedef enum {
EXCEPTION_NONE = 0,
EXCEPTION_DIVISION_BY_ZERO,
EXCEPTION_NULL_POINTER,
EXCEPTION_OUT_OF_RANGE
} Exception;
const char* exception_messages[] = {
"无异常",
"除零错误",
"空指针错误",
"超出范围错误"
};
void throw(Exception ex) {
printf("抛出异常: %s\n", exception_messages[ex]);
longjmp(exception_env, ex);
}
double divide(double a, double b) {
if (b == 0.0) {
throw(EXCEPTION_DIVISION_BY_ZERO);
}
return a / b;
}
void process_pointer(int* ptr) {
if (ptr == NULL) {
throw(EXCEPTION_NULL_POINTER);
}
*ptr = 100;
}
void check_range(int value, int min, int max) {
if (value < min || value > max) {
throw(EXCEPTION_OUT_OF_RANGE);
}
}
int main() {
Exception ex;
if ((ex = setjmp(exception_env)) == EXCEPTION_NONE) {
printf("开始处理\n");
double result = divide(10.0, 2.0);
printf("10.0 / 2.0 = %.2f\n", result);
result = divide(10.0, 0.0);
printf("这行不会执行\n");
} else {
printf("捕获异常: %s\n", exception_messages[ex]);
}
if ((ex = setjmp(exception_env)) == EXCEPTION_NONE) {
printf("\n开始处理指针\n");
int value;
process_pointer(&value);
printf("指针处理成功: %d\n", value);
process_pointer(NULL);
printf("这行不会执行\n");
} else {
printf("捕获异常: %s\n", exception_messages[ex]);
}
return 0;
}6. 多级错误处理
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf level1_env;
jmp_buf level2_env;
jmp_buf level3_env;
void level3() {
printf("进入第 3 级\n");
if (setjmp(level3_env) == 0) {
printf("第 3 级处理中...\n");
longjmp(level3_env, 1);
} else {
printf("第 3 级错误处理\n");
longjmp(level2_env, 1);
}
}
void level2() {
printf("进入第 2 级\n");
if (setjmp(level2_env) == 0) {
printf("第 2 级处理中...\n");
level3();
} else {
printf("第 2 级错误处理\n");
longjmp(level1_env, 1);
}
}
void level1() {
printf("进入第 1 级\n");
if (setjmp(level1_env) == 0) {
printf("第 1 级处理中...\n");
level2();
} else {
printf("第 1 级错误处理\n");
}
}
int main() {
level1();
printf("回到主函数\n");
return 0;
}7. 超时处理
c
#include <stdio.h>
#include <setjmp.h>
#include <time.h>
#include <unistd.h>
jmp_buf timeout_env;
void timeout_handler() {
printf("超时!\n");
longjmp(timeout_env, 1);
}
void long_operation() {
printf("开始长时间操作...\n");
for (int i = 0; i < 10; i++) {
printf("进度: %d/10\n", i + 1);
sleep(1);
if (i == 5) {
printf("操作被中断\n");
longjmp(timeout_env, 1);
}
}
printf("操作完成\n");
}
int main() {
if (setjmp(timeout_env) == 0) {
printf("开始执行\n");
long_operation();
} else {
printf("操作超时或被中断\n");
}
return 0;
}注意事项
1. 跳转范围
longjmp() 只能跳转到当前活动栈帧中的 setjmp() 调用点。
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void outer_function() {
if (setjmp(env) == 0) {
printf("outer_function: setjmp\n");
inner_function();
} else {
printf("outer_function: longjmp 返回\n");
}
}
void inner_function() {
printf("inner_function: 调用 longjmp\n");
longjmp(env, 1);
}
int main() {
outer_function();
return 0;
}2. 变量值
longjmp() 跳转后,自动变量的值可能不确定,除非声明为 volatile。
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void function() {
int x = 10;
volatile int y = 20;
if (setjmp(env) == 0) {
printf("第一次: x = %d, y = %d\n", x, y);
x = 30;
y = 40;
longjmp(env, 1);
} else {
printf("第二次: x = %d, y = %d\n", x, y);
}
}
int main() {
function();
return 0;
}3. 资源管理
使用 setjmp/longjmp 时要确保资源正确释放。
c
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
jmp_buf env;
void* resource = NULL;
void cleanup() {
if (resource != NULL) {
free(resource);
resource = NULL;
}
}
void allocate() {
resource = malloc(1024);
if (resource == NULL) {
longjmp(env, 1);
}
}
int main() {
if (setjmp(env) == 0) {
allocate();
printf("资源分配成功\n");
} else {
printf("资源分配失败\n");
}
cleanup();
return 0;
}4. 返回值
longjmp() 的返回值不能为 0,如果传递 0 会被转换为 1。
c
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void function() {
longjmp(env, 0);
}
int main() {
int ret = setjmp(env);
printf("返回值: %d\n", ret);
if (ret == 0) {
function();
}
return 0;
}总结
<setjmp.h> 提供的非本地跳转机制是一种强大的错误处理和异常处理工具,但需要谨慎使用:
- 适用场景 - 错误处理、异常处理、深度递归退出
- 注意事项 - 变量值、资源管理、跳转范围
- 替代方案 - 考虑使用传统的错误处理机制
记住:
setjmp/longjmp会绕过正常的函数返回机制- 使用
volatile关键字保护重要的变量 - 确保资源正确释放
- 不要过度使用,优先使用传统的错误处理