文章目录
- Linux 编程中的错误处理机制详解 —— `errno` 全解析
- 一、什么是 `errno`?
- ❓为什么需要 `errno`?
- ✅ 它在哪里定义?
- 二、errno 的设置与读取规则
- ⚠️ errno 不是总是有效!
- ❗使用 errno 的正确步骤:
- 三、与 errno 配套使用的函数
- 示例:
- 四、errno 常见错误码及含义
- 五、errno 是线程安全的吗?
- 六、errno 封装建议
- 七、errno 与系统调用的配合使用
- open() 示例:
- read() 示例:
- 八、如何查看 errno 对应的错误码?
- 九、调试技巧:gdb 查看 errno
- 🔟 附录:示例程序 - 模拟打开文件失败
- ✅ 总结
Linux 编程中的错误处理机制详解 —— errno
全解析
在 Linux C 系统编程中,我们时常会与各种系统调用打交道,如 open()
、read()
、write()
、fork()
、socket()
等。错误处理是程序鲁棒性的重要组成部分,而 errno
就是标准库为我们提供的重要错误处理机制。本文将全面详细讲解 errno
的原理、用法、注意事项、线程安全特性及实践案例。
一、什么是 errno
?
errno
是一个全局变量,代表最近一次系统调用或标准库函数失败时的错误码。它是 int
类型,但为了线程安全,现代实现中实际为“线程局部存储”(TLS),即每个线程有自己的 errno 值。
❓为什么需要 errno
?
很多系统调用或库函数在失败时只返回 -1 或 NULL,并不告诉你具体错误信息。此时就需要查询 errno
来获得错误原因。
✅ 它在哪里定义?
你需要引入头文件:
#include <errno.h>
它在 <errno.h>
中的定义通常为:
extern int errno;
但实际实现如下(glibc):
#define errno (*__errno_location()) // 或 __errno()
意味着每个线程调用 errno
实际是访问一个局部的 int
指针,避免线程间干扰。
二、errno 的设置与读取规则
⚠️ errno 不是总是有效!
- 仅当系统调用 失败时,系统才会设置
errno
。 - 成功调用不会清除
errno
,所以你不能通过errno == 0
判断成功与否。
❗使用 errno 的正确步骤:
errno = 0; // 可选,清除旧错误
int fd = open("nofile.txt", O_RDONLY);
if (fd == -1) {// open 失败,可以读取 errnoprintf("Error code: %d\n", errno);printf("Message: %s\n", strerror(errno));
}
三、与 errno 配套使用的函数
函数 | 作用 |
---|---|
perror() | 打印系统调用错误及自定义前缀 |
strerror(errno) | 返回错误码对应的错误字符串 |
strerror_r() | 线程安全版本,写入用户缓冲区 |
示例:
if ((fp = fopen("test.txt", "r")) == NULL) {perror("fopen");fprintf(stderr, "详细错误:%s\n", strerror(errno));
}
四、errno 常见错误码及含义
宏名 | 值 | 说明 |
---|---|---|
EPERM | 1 | 操作不被允许 |
ENOENT | 2 | 文件或目录不存在 |
ESRCH | 3 | 没有匹配的进程 |
EINTR | 4 | 系统调用中断 |
EIO | 5 | I/O 错误 |
EBADF | 9 | 无效的文件描述符 |
EACCES | 13 | 权限不足 |
EEXIST | 17 | 文件已存在 |
ENOTDIR | 20 | 不是目录 |
EISDIR | 21 | 是目录 |
EINVAL | 22 | 参数无效 |
ENFILE | 23 | 系统打开的文件数已达上限 |
EMFILE | 24 | 进程打开的文件数已达上限 |
ENOSPC | 28 | 设备空间不足 |
可通过 man 3 errno
查看完整列表。
五、errno 是线程安全的吗?
是的,在现代 glibc 实现中是线程安全的。
虽然 errno
看起来像一个全局变量,但实际是通过 __errno_location()
这样的函数返回每个线程自己的 errno 变量,确保在多线程中使用不会冲突。
六、errno 封装建议
为了代码复用和统一日志格式,可以封装错误打印:
void report_errno(const char* msg) {fprintf(stderr, "[ERROR] %s: %s (errno: %d)\n", msg, strerror(errno), errno);
}
使用示例:
if (close(fd) == -1) {report_errno("close failed");
}
七、errno 与系统调用的配合使用
open() 示例:
int fd = open("test.txt", O_RDONLY);
if (fd < 0) {perror("open");
}
read() 示例:
char buffer[128];
int n = read(fd, buffer, sizeof(buffer));
if (n < 0) {fprintf(stderr, "read error: %s\n", strerror(errno));
}
八、如何查看 errno 对应的错误码?
方法一:终端查看 man 手册
man 3 errno
man 3 strerror
方法二:查看头文件定义
cat /usr/include/asm-generic/errno-base.h
cat /usr/include/asm-generic/errno.h
方法三:自定义测试代码输出:
for (int i = 0; i < 50; ++i) {printf("errno %d: %s\n", i, strerror(i));
}
九、调试技巧:gdb 查看 errno
在 GDB 中调试程序出错时,可使用:
(gdb) print errno
(gdb) print strerror(errno)
确保你在出错系统调用之后立即查看,否则 errno 值可能被覆盖。
🔟 附录:示例程序 - 模拟打开文件失败
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>int main(void) {int fd = open("nonexist.txt", O_RDONLY);if (fd == -1) {fprintf(stderr, "打开失败: %s (errno = %d)\n", strerror(errno), errno);return 1;}close(fd);return 0;
}
✅ 总结
关键点 | 说明 |
---|---|
errno 的本质 | 一个线程局部变量,记录最近一次失败的错误码 |
设置时机 | 系统调用或函数失败时设置,成功时不修改 |
获取错误信息方式 | strerror(errno) 获取描述,perror() 自动输出 |
多线程安全性 | 是,现代系统通过线程局部存储实现 |
使用注意事项 | 只能在调用失败时使用,切勿误判 |