C语言错误处理与调试技巧完全指南

健壮的错误处理和高效的调试技巧是 C 语言编程的核心能力。本文将详细介绍 errno、错误码设计、断言使用以及 GDB 调试技巧,帮助你编写更可靠的 C 程序。

错误处理基础

C 语言中的错误处理主要通过以下机制实现:

  • 返回值 - 函数返回错误码
  • errno - 系统级错误码
  • setjmp/longjmp - 非局部跳转
  • 断言 - 开发期检查

errno 的使用

标准库函数失败时会设置 errno,需要在调用前将其置零:

errno_usage.c
#include 
#include 
#include 
#include 

int main(void) {
    /* 使用 fopen 打开不存在的文件 */
    errno = 0;
    FILE *fp = fopen("nonexistent.txt", "r");

    if (fp == NULL) {
        fprintf(stderr, "打开文件失败: %s\n", strerror(errno));
        return 1;
    }

    fclose(fp);
    return 0;
}

perror 函数

perror() 直接输出错误信息,无需手动处理 errno:

perror_usage.c
#include 
#include 

int main(void) {
    FILE *fp = fopen("/root/secret.txt", "r");

    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }

    fclose(fp);
    return 0;
}

输出示例:打开文件失败: Permission denied

自定义错误码

在库或大型项目中,应定义自己的错误码:

error_codes.h
/* 自定义错误码 */
#define ERR_OK             0
#define ERR_INVALID_PARAM  1
#define ERR_MALLOC_FAIL   2
#define ERR_FILE_NOT_FOUND 3
#define ERR_PARSE_FAIL    4
#define ERR_BUFFER_FULL   5

/* 错误码转字符串 */
const char *err_string(int err) {
    switch(err) {
        case ERR_OK:             return "Success";
        case ERR_INVALID_PARAM:  return "Invalid parameter";
        case ERR_MALLOC_FAIL:   return "Memory allocation failed";
        case ERR_FILE_NOT_FOUND: return "File not found";
        case ERR_PARSE_FAIL:    return "Parse failed";
        case ERR_BUFFER_FULL:   return "Buffer is full";
        default:                return "Unknown error";
    }
}

/* 示例使用 */
int process_data(const char *input) {
    if (input == NULL) {
        return ERR_INVALID_PARAM;
    }

    /* 处理逻辑... */
    return ERR_OK;
}

断言的使用

assert() 用于开发期检查不应该发生的情况:

assert_usage.c
#include 
#include 
#include 

int divide(int a, int b) {
    /* 断言:除数不能为零 */
    assert(b != 0);
    return a / b;
}

int main(void) {
    printf("10 / 2 = %d\n", divide(10, 2));

    /* 这会导致断言失败 */
    printf("10 / 0 = %d\n", divide(10, 0)); */

    return 0;
}

编译时加 -DNDEBUG 可禁用所有断言:

gcc -o prog prog.c -DNDEBUG

GDB 调试技巧

常用 GDB 命令:

  • gdb ./program - 启动调试
  • runr - 运行程序
  • break funcb func - 在函数入口设断点
  • break 10 - 在第 10 行设断点
  • continuec - 继续执行
  • nextn - 单步执行(不进入函数)
  • steps - 单步执行(进入函数)
  • print varp var - 打印变量值
  • backtracebt - 查看调用栈
  • info locals - 查看局部变量
gdb_demo.c
#include 
#include 

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main(void) {
    int result = fibonacci(10);
    printf("fibonacci(10) = %d\n", result);
    return 0;
}

调试步骤:

gdb ./a.out
(gdb) break fibonacci
(gdb) run
(gdb) bt                    # 查看调用栈
(gdb) p n                   # 查看参数
(gdb) continue
(gdb) p result              # 查看返回值
(gdb) quit

条件断点

设置条件断点,只在满足条件时暂停:

(gdb) break main.c:20 if i == 100

这会在第 20 行当变量 i 等于 100 时中断。

观察点

使用 watch 观察变量变化:

(gdb) watch variable_name   # 变量变化时暂停
(gdb) rwatch variable_name  # 变量被读取时暂停
(gdb) awatch variable_name  # 变量被读写时暂停

总结

良好的错误处理习惯包括:始终检查返回值、为库定义清晰的错误码、在开发期使用断言捕获不可能的情况、熟练掌握 GDB 调试技能。养成这些习惯能显著提高代码质量和调试效率。