Linux 系统错误处理简介
- 1. `errno`:错误代码的载体
- 2. `strerror()`:错误信息的翻译官
- 3. `perror()`:便捷的错误信息输出
- 4. 系统调用与库函数的区别
- 5. 错误处理的最佳实践
在 C/C++ 程序开发中,我们经常需要处理各种错误情况
Linux 系统提供了一套完善的错误处理机制,通过全局变量 errno
和相关的库函数,我们可以方便地定位和处理错误
1. errno
:错误代码的载体
errno
是一个整型的全局变量,定义在 <errno.h>
头文件中。当一个函数调用发生错误时,通常会将相应的错误代码存储在 errno
中。通过检查 errno
的值,我们可以判断函数调用是否成功以及失败的原因。
需要注意的是:
- 并非所有库函数在调用失败时都会设置
errno
的值。只有属于系统调用的函数才会设置errno
。可以通过man
手册来确认某个函数是否会设置errno
。 errno
不能作为判断库函数调用是否失败的唯一标志。正确的做法是先检查函数的返回值,只有当返回值表明函数调用失败时,才需要关注errno
的值。errno
的值只有在库函数调用发生错误时才会被设置,当库函数调用成功时,errno
的值不会被修改,也不会主动置为 0。
2. strerror()
:错误信息的翻译官
strerror()
函数定义在 <string.h>
头文件中,用于将错误代码转换为相应的错误信息字符串。
char *strerror(int errnum); // 非线程安全
int strerror_r(int errnum, char *buf, size_t buflen); // 线程安全
其中,errnum
是错误代码,strerror()
返回一个指向错误信息字符串的指针。strerror_r()
是 strerror()
的线程安全版本,它将错误信息存储在用户提供的缓冲区 buf
中。
3. perror()
:便捷的错误信息输出
perror()
函数定义在 <stdio.h>
头文件中,用于在控制台输出最近一次系统错误的详细信息。
void perror(const char *s);
perror()
接受一个字符串 s
作为参数,它会将 s
和最近一次系统错误的描述信息一起输出到标准错误流(stderr
)。在实际开发中,由于服务程序通常在后台运行,通过控制台显示错误信息意义不大。perror()
主要用于调试程序。
4. 系统调用与库函数的区别
在讨论 errno
的适用范围时,我们提到了“系统调用”的概念。理解系统调用与库函数的区别对于理解 Linux 系统错误处理至关重要。
系统调用是操作系统内核提供给用户程序的一组接口,用于访问操作系统提供的各种服务,例如文件 I/O、进程管理、内存管理等。系统调用直接与内核交互,运行在内核态。
库函数是程序员为了方便开发而封装的一系列函数,它们通常是对系统调用的封装或组合,也可能不涉及系统调用。库函数运行在用户态。
以下是系统调用与库函数的一些主要区别:
特性 | 系统调用 | 库函数 |
---|---|---|
定义 | 操作系统内核提供的接口 | 程序员封装的函数 |
运行空间 | 内核态 | 用户态 |
可移植性 | 不同操作系统之间通常不同 | 遵循标准(如 ANSI C)则可移植性较好 |
性能 | 开销较大(需要用户态和内核态切换) | 开销较小 |
功能 | 提供最基本、最底层的操作系统服务 | 提供更高级、更方便的功能 |
与 errno | 通常会设置 errno | 并非都会设置 errno |
举例来说,open()
、read()
、write()
等是系统调用,而 fopen()
、fread()
、fwrite()
等是库函数。fopen()
等库函数底层会调用相应的系统调用来完成文件操作。
5. 错误处理的最佳实践
在实际开发中,我们应该遵循以下几点:
- 始终检查函数的返回值,判断函数调用是否成功。
- 只有当返回值表明函数调用失败时,才需要关注
errno
的值。 - 使用
strerror()
或perror()
获取详细的错误信息,方便定位问题。 - 根据不同的错误类型采取相应的处理措施,例如重试、记录日志、退出程序等。
- 在多线程环境下,应使用
strerror_r()
等线程安全的函数。