C语言的setjmp和longjmp:可以作异常处理
前言
可以把setjmp和longjmp想象成"设置检查点"和"时空穿越":
- setjmp就像在游戏里存档,标记一个位置
- longjmp就像读取存档,直接跳回那个位置
这两个函数主要用在什么场景呢?
- 错误处理:在深层函数中发生错误时,直接跳回到之前设置的处理点
- 异常处理:在C语言中模拟类似try-catch的功能
- 协程实现:保存和恢复执行状态
让我写个简单的例子来说明:
示例1:简单的异常处理
#include <stdio.h>
#include <setjmp.h>
// 定义一个跳转缓冲区,用来保存"存档点"的状态
jmp_buf jump_buffer;
// 模拟一个可能出错的函数
void deep_function(int value) {
printf("进入 deep_function,参数值:%d\n", value);
if (value < 0) {
printf("哎呀,发现负数,准备跳回!\n");
// 跳回到之前设置的点,并带回错误码1
longjmp(jump_buffer, 1);
}
printf("deep_function 正常结束\n");
}
int main() {
printf("程序开始\n");
// setjmp 返回值很特殊:
// - 第一次调用时返回0
// - 从longjmp跳回时返回longjmp设置的值
int ret = setjmp(jump_buffer);
if (ret == 0) {
printf("设置跳转点\n");
// 正常执行流程
deep_function(-1); // 传入一个负数
printf("这行不会被执行到\n");
} else {
// 从longjmp跳回后的处理
printf("跳回到跳转点,错误码:%d\n", ret);
}
printf("程序结束\n");
return 0;
}
运行结果会是:
程序开始
设置跳转点
进入 deep_function,参数值:-1
哎呀,发现负数,准备跳回!
跳回到跳转点,错误码:1
程序结束
示例2:稍微复杂的异常处理
#include <stdio.h>
#include <setjmp.h>
#include <string.h>
// 定义错误类型
#define ERR_FILE_NOT_FOUND 1
#define ERR_INVALID_DATA 2
// 全局跳转缓冲区
jmp_buf error_jump_buffer;
// 模拟抛出异常
void throw_error(int error_code) {
longjmp(error_jump_buffer, error_code);
}
// 模拟文件操作
void read_file(const char* filename) {
printf("尝试读取文件:%s\n", filename);
// 模拟文件不存在
if (strcmp(filename, "not_exist.txt") == 0) {
printf("文件不存在!\n");
throw_error(ERR_FILE_NOT_FOUND);
}
// 模拟文件数据无效
if (strcmp(filename, "invalid.txt") == 0) {
printf("文件数据无效!\n");
throw_error(ERR_INVALID_DATA);
}
printf("文件读取成功\n");
}
// 模拟处理文件
void process_file(const char* filename) {
printf("\n开始处理文件:%s\n", filename);
// 设置错误处理点
int error_code = setjmp(error_jump_buffer);
if (error_code == 0) {
// 正常执行流程
read_file(filename);
printf("文件处理完成\n");
} else {
// 错误处理
switch (error_code) {
case ERR_FILE_NOT_FOUND:
printf("错误处理:文件未找到\n");
break;
case ERR_INVALID_DATA:
printf("错误处理:无效数据\n");
break;
default:
printf("错误处理:未知错误 %d\n", error_code);
}
}
}
int main() {
// 测试不同情况
process_file("normal.txt"); // 正常文件
process_file("not_exist.txt"); // 不存在的文件
process_file("invalid.txt"); // 无效数据的文件
return 0;
}
总结:
需要注意的几点:
使用限制:
- setjmp必须在longjmp之前调用
- longjmp不能跳转到已经返回的函数
- 局部变量在longjmp后的值是不确定的(除非声明为volatile)
潜在风险:
- 过度使用会使程序流程难以理解
- 可能导致资源泄露(因为跳过了正常的清理代码)
- 不能跨线程使用
最佳实践:
- 主要用于错误处理
- 尽量在较小的范围内使用
- 确保正确处理资源清理
- 现代C++程序应该使用异常处理机制