健壮的错误处理和高效的调试技巧是 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 - 启动调试
- run 或 r - 运行程序
- break func 或 b func - 在函数入口设断点
- break 10 - 在第 10 行设断点
- continue 或 c - 继续执行
- next 或 n - 单步执行(不进入函数)
- step 或 s - 单步执行(进入函数)
- print var 或 p var - 打印变量值
- backtrace 或 bt - 查看调用栈
- 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 调试技能。养成这些习惯能显著提高代码质量和调试效率。