在 C 语言中,没有内置的异常支持,错误处理是通过以下几种方式实现的:
返回错误代码(通常通过函数的返回值) 示例:
int read_file(const char *name) { FILE *f = fopen(name, "r"); if (!f) return -1; // 错误 // ... fclose(f); return 0; // 成功 }
使用全局变量 errno
当出错时,标准函数通常会设置全局变量 errno。
示例:
FILE *fp = fopen("nofile", "r"); if (!fp) { perror("文件错误"); // errno 定义了原因 }
setjmp/longjmp — 抛出“异常”的机制(保存和恢复执行上下文) 用于复杂的情况(例如,跨层级的紧急退出)。使用频率较低,因为它会增加代码复杂性并可能导致资源泄露。
#include <setjmp.h> jmp_buf buf; if (setjmp(buf) == 0) { // ... longjmp(buf, 1); // 跳转到 if (setjmp(...)) } else { // 错误处理 }
下列代码将输出什么?
#include <stdio.h> #include <setjmp.h> jmp_buf buf; void func() { longjmp(buf, 5); } int main() { int val = setjmp(buf); printf("val=%d\n", val); if (val == 0) func(); return 0; }
答案:
setjmp 返回 0 并调用函数 func。longjmp(buf, 5),执行返回到 setjmp,此时返回 5。val=0
val=5
故事 在某个库中实现了仅基于返回代码的手动资源清理机制。但程序员在从回调中返回(通过 setjmp/longjmp)时忘记处理错误,结果导致内存泄漏和文件锁定。
故事 在网络应用中遗漏了在 socket 操作后检查
errno。因此,出现了错误的诊断:变量errno保留了来自先前函数的旧的、不正确的值。
故事 团队实现了复杂的嵌套,具有多个返回错误代码的点,但没有遵循返回值(零/负值)的约定。某些错误被解释为成功,导致服务无法预测的故障。