1.7 错误处理
当UNIX系统函数发生错误时,通常返回一个负值,并且整数errno被设置为一个可以给出额外信息的值。例如,open函数或者返回一个非负的文件描述符(当一切正常时),或者产生一个错误。一个open的错误能产生15个可能的errno值,例如文件不存在,权限问题,等等。一些函数不返回负值,而是使用习惯方法来表示错误。例如,多数函数返回一个对象的指针,而返回一个null(空)指针来表示一个错误。
头文件<errno.h>定义了标识符errno和errno的每个可能的常量值。这之中的每个常量值都以字符E开头。在UNIX系统手册第二节的第一页,名为intro(2)的页面中,同样列出了这之中所有的错误常量。例如,如果errno等于常量EACCES,这就显示了一个权限错误,比如没有足够的权限来打开所请求的文件。
在Linux中,错误常量被列举在手册errno(3)中。
POSIX和ISO C把errno扩展定义为可变的整型左值。它既可以是一个包含了错误代码的整数,也可以是一个函数,该函数返回指向错误代码的指针。以前的定义是
extern int errno;
然而在一个支持线程的环境中,进程地址空间在多个线程中共享,同时每个线程都需要errno的本地拷贝来防止线程间互相影响。例如,Linux通过以下定义来支持多线程访问errno:
extern int *_ _errno_location(void);
#define errno (*_ _errno_location())
errno有两条规则。第一,如果不发生错误,errno的值决不会被程序清除。因此,只有在函数的返回值表示错误发生时,才需要检查errno的值。第二,任何函数都不会把errno的值设置为0,同时在<errno.h>中也没有定义任何常量值为0。
标准C中定义了两个函数来帮助打印错误消息。
#include <string.h>
char *strerror(int errnum);
返回值:指向消息字符串的指针 |
该函数把errno的典型值errnum映射到一个错误消息字符串,并返回一个指向字符串的指针。
perror函数在标准错误产生并返回一个错误消息,该消息基于errno的当前值。
#include <stdio.h>
void perror(const char *msg); |
它输出msg指向的字符串,接着是一个分号和一个空格,然后是与errno值对应的错误消息,最后是一个新行。
例子
图1.8展示了这两个函数的应用。
如果该程序被编译为文件a.out,我们将看到
$ ./a.out
EACCES: Permission denied
./a.out: No such file or directory
注意我们把程序名字argv[0]作为参数传递给perror,argv[0]的值是./a.out。这是UNIX系统的一个标准惯例。通过这样做,如果程序是作为管道的一部分执行,就像在
prog1 < inputfile | prog2 | prog3 > outputfile
我们就能够分清是三个程序中是哪个产生了错误消息。
1#include "apue.h"
2#include <errno.h>
3
4int
5main(int argc, char *argv[])
6{
7 fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
8 errno = ENOENT;
9 perror(argv[0]);
10 exit(0);
11}
图 1.8 strerror和perror的示范
本书中的所有的例子都使用附录B中的错误函数,来代替直接调用strerror或者perror。附录中的错误函数使用了ISO C的可变参数列表,可以只用单个C语句来处理错误情况。
错误恢复
<errno.h>中定义的错误可以被分为两类:致命的和非致命的。一个致命错误是不能够恢复的。最好的办法是在用户的屏幕上打印一条错误消息,或者在一个日志文件中写入错误消息,接着在退出。另一方面,非致命错误在某些时候能够更得体的处理。多数非致命错误是自然的临时错误,例如当系统的活动程序较少时,(系统)资源短缺的错误可能就不会发生。
资源相关的非致命错误包括EAGAIN,ENFILE,ENOBUFS,ENOLCK,ENOSPC,ENOSR,EWOULDBLOCK,当ENOMEM,EBUSH表示一个共享资源正在被使用时,它们也可以被作为非致命错误。某些时候,当EINTR中断了一个缓慢的系统调用时,它也可以被看作非致命错误(详见10.5节)。
资源相关的非致命错误的典型恢复动作就是延迟一会儿再试。这个技巧也能应用在其它循环中。例如,如果错误表示网络连接没有工作,那么程序可能会延迟一会儿再重新建立连接。一些程序使用指数增长的算法,每次等待更长的时间。
最后,由应用程序开发者来决定哪些错误是可以恢复的。如果一个合理的策略能够被用来恢复错误,通过避免异常退出,我们就可以增强我们程序的健壮性。