这篇博客介绍了unix错误处理中重要的概念:
errno
,介绍了它的定义,作用和注意事项
一. 错误总是不可避免的...
在计算机中因为各种内外部原因,错误是不可避免的,异常处理是程序的组成部分。
比如,我们想获取一个文件的基本信息,可以调用系统函数
int stat(const char *restrict pathname, struct stat *restrict buf);
如果成功,文件的信息会通过第二个参数buf
返回;但也可能会失败,失败的原因可能是
文件不存在,也可能是没有访问权限或者其他原因。
那么操作系统如何告诉我们发生了错误?更进一步,操作系统如何告知调用者错误的具体
原因呢?
根据stat
函数的man文档,我们知道,可以通过返回值来判断是否成功,返回值等于0表
示成功,返回-1表示失败
那具体错误原因呢?这时errno就登场了,errno相当于一个错误码,当stat
发生
错误时,会设置errno的值,我们可以通过检查errno来得到具体的错误信息。
一般的代码片段如下:
struct stat sb;
if (stat("./not_exist.txt", &sb) == -1)
{
printf("errorno is: %d\n", errno);
printf("error msg produced by strerror(): %s\n", strerror(errno));
perror("error msg produced by perror");
}
上述代码中,strerror
和perror
是两个有用的函数。strerror
能够将errno
转化为
对应错误信息的字符串,perror
能够直接打印出错误信息,errno
没有出现在perror
的输入参数中,这个问题后面在讲
二 常见错误码
Linux中可以通过man errno.3
指令来查看常见错误码,错误码作为常量定义在errno.h
文件中,常见的错误码定义如下(取自errno-bash.h文件):
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
错误码可以分为两类:致命错误和非致命错误。
致命错误无法恢复,程序只能立刻终止运行
非致命错误大多数情况下暂时的(比如网络短暂异常),程序可以处理这些错误,稍后重试,
增强程序健壮性
三 注意事项
errno
有两个性质:
- 正常的系统调用不会改变
errno
的值,只有错误的系统调用才会修改errno
的值
这意味着,errno
始终记录着最近一次调用错误的错误码,我们不能通过检查errno
来
判断是否发生错误,是否发生错误需要通过系统调用的返回值来判断,在确定发生了错误
之后,在去检查errno
获得具体的错误原因。
这个性质也是一开始提到的perror
不需要errno
作为参数的原因。 -
errno
不会等于0
四 errno的内部实现
errno
是一个整数,可以简单实现为:
extern int errno
上面的实现方式非常简单,可惜在多线程的情况下会发生错误,因为当多个线程需要同时修改
errno
时,前一个线程的值会被后一个线程覆盖,因此,每一个现成需要自己的errno
Linux中,errno的真实定义是:
extern int *_ _errno_location(void);
#define errno (*_ _errno_location())
例子
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h> //strerror()
/**
* When an error occurs in one of the UNIX System functions, a negative value
* is often returned, and the integer errno is usually set to a value that
* tells why
*/
int
main(int argc, char* argv[])
{
struct stat sb;
if (stat("./not_exist.txt", &sb) == -1)
{
printf("errorno is: %d\n", errno);
printf("error msg produced by strerror(): %s\n", strerror(errno));
perror("error msg produced by perror");
}
if (stat("./errno.c", &sb) == 0)
{
//errno is never cleared by a routine if an error does not occur
perror("last error msg is");
}
}
输出结果为:
errorno is: 2
error msg produced by strerror(): No such file or directory
error msg produced by perror: No such file or directory
last error msg is: No such file or directory
参考资料
- Linux系统文件:
/usr/include/asm-generic/errno-base.h
- Advanced Programming in the Unix Environment(3rd) P14 Error Handling