Linux 错误处理
在 Linux C 编程中,很多系统调用和标准库函数都会通过返回值来表示是否出错。但是,返回值只能告诉你“出错了”,却无法告诉你为什么出错,这时候你就需要使用全局变量错误号 errno 和 perror() 函数来获取和打印错误信息,帮助快速定位错误原因。
本文将带你了解:
errno是什么,怎么使用?perror和strerror的区别与用法;- 常见的错误码及其含义;
- 使用
errno的注意事项; - 一个完整的错误处理示例。
什么是 errno?
errno 是一个全局变量,用于存储最近一次错误的错误代码(错误号)。当你调用某个函数失败时,比如 open()、read()、malloc() 等,它们通常会设置 errno 为一个具体的错误编号。
这个编号是一个整数,例如:
2表示ENOENT:文件不存在;13表示EACCES:权限不足;12表示ENOMEM:内存不足。
你可以在头文件中找到这些定义:
#include <errno.h>
⚠️ 注意:errno 不是一个普通变量,而是一个“线程局部”的宏,底层通过 TLS(线程局部存储)实现。它对多线程是安全的。
使用 perror 打印错误信息
你可以使用 perror() 来根据当前的 errno 值打印一条标准错误信息,非常方便调试:
#include <stdio.h>
perror("open"); // 输出类似 "open: No such file or directory"
这会输出你传入的字符串加上对应的错误信息,自动根据当前 errno 转换为人类可读的字符串。
使用 strerror 获取错误字符串
如果你想自己控制错误信息的格式,可以用 strerror():
#include <string.h>
printf("错误信息:%s\n", strerror(errno));
你也可以用它自定义日志输出:
fprintf(stderr, "打开文件失败(错误号 %d):%s\n", errno, strerror(errno));
示例:打开文件失败时的错误处理
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("not_exist.txt", O_RDONLY);
if (fd == -1) {
// 方法一:用 perror 打印
perror("打开文件失败");
// 方法二:用 strerror 打印
fprintf(stderr, "错误号 %d:%s\n", errno, strerror(errno));
return 1;
}
close(fd);
return 0;
}
运行结果(假设文件不存在)可能是:
打开文件失败: No such file or directory
错误号 2:No such file or directory
使用 errno 的注意事项
- ⚠️ 不要手动清零 errno,也不要依赖它的初始值。
- errno 只有在出错时才有意义,函数成功不会重置它;
- 多线程程序中,
errno是线程安全的; - 某些库函数不会设置 errno,因此具体使用要看文档;
- 如果你要在调用失败后马上用
errno,中间不要调用其他函数,以免被覆盖。
errno 错误码列表
完整的 Linux 错误代码及其含义如下表所示。常见的错误有 ENOENT、EACCES、ENOMEM、EAGAIN、EBADF、EEXIST、EINVAL、ECONNREFUSED 等等,你也可以通过 man 3 errno 查看详细文档。
| 宏名 | errno | 描 述 | 含义 |
|---|---|---|---|
| EPERM | 1 | Operation not permitted | 操作不允许 |
| ENOENT | 2 | No such file or directory | 文件或目录不存在 |
| ESRCH | 3 | No such process | 没有这样的过程 |
| EINTR | 4 | Interrupted system call | 系统调用被中断 |
| EIO | 5 | I/O error | I/O 错误 |
| ENXIO | 6 | No such device or address | 设备或地址不存在 |
| E2BIG | 7 | Arg list too long | 参数列表太长 |
| ENOEXEC | 8 | Exec format error | 执行格式错误 |
| EBADF | 9 | Bad file number | 坏的文件描述符 |
| ECHILD | 10 | No child processes | 没有子进程 |
| EAGAIN | 11 | Try again | 资源暂时不可用(非阻塞 I/O) |
| ENOMEM | 12 | Out of memory | 内存溢出(内存不足) |
| EACCES | 13 | Permission denied | 拒绝许可(权限不够) |
| EFAULT | 14 | Bad address | 错误的地址 |
| ENOTBLK | 15 | Block device required | 块设备请求 |
| EBUSY | 16 | Device or resource busy | 设备或资源忙 |
| EEXIST | 17 | File exists | 文件已存在 |
| EXDEV | 18 | Cross-device link | 无效的交叉链接 |
| ENODEV | 19 | No such device | 设备不存在 |
| ENOTDIR | 20 | Not a directory | 不是一个目录 |
| EISDIR | 21 | Is a directory |