编程C 开发者

如何在 C 语言中实现错误处理?setjmp/longjmp、errno 和错误代码返回之间有什么区别?何时使用这些方法?

用 Hintsage AI 助手通过面试

答案

在 C 语言中,没有内置的异常支持,错误处理是通过以下几种方式实现的:

  1. 返回错误代码(通常通过函数的返回值) 示例:

    int read_file(const char *name) { FILE *f = fopen(name, "r"); if (!f) return -1; // 错误 // ... fclose(f); return 0; // 成功 }
  2. 使用全局变量 errno 当出错时,标准函数通常会设置全局变量 errno。 示例:

    FILE *fp = fopen("nofile", "r"); if (!fp) { perror("文件错误"); // errno 定义了原因 }
  3. setjmp/longjmp — 抛出“异常”的机制(保存和恢复执行上下文) 用于复杂的情况(例如,跨层级的紧急退出)。使用频率较低,因为它会增加代码复杂性并可能导致资源泄露。

    #include <setjmp.h> jmp_buf buf; if (setjmp(buf) == 0) { // ... longjmp(buf, 1); // 跳转到 if (setjmp(...)) } else { // 错误处理 }
  • 返回错误代码 — 主要方法,简单且安全。
  • errno — 对于库函数很方便。
  • setjmp/longjmp — 对于特定情况(复杂库或解释器)。

含有陷阱的问题

下列代码将输出什么?

#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 保留了来自先前函数的旧的、不正确的值。


故事 团队实现了复杂的嵌套,具有多个返回错误代码的点,但没有遵循返回值(零/负值)的约定。某些错误被解释为成功,导致服务无法预测的故障。