1.6 错误处理
UNIX的系统调用和大部分库函数在失败时会返回一特殊值报告出错,这个特殊值通常是–1。这种返回值仅告诉调用遇到了错误而已,为了让应用知道究竟发生了什么错误,系统调用和库函数同时还会在系统定义的变量errno中给出指明错误原因的错误码。
变量errno是系统调用和库函数用来报告错误的一种标准方法。早期的UNIX和C将它实现为外部整型变量,其说明为:
extern int errno;
这是进程中的全局共享变量,无法支持多线程。为了支持多线程,新的POSIX标准和C标准在要求这个变量为整型左值的同时,还规定一个线程对errno的读写不受其他线程更改errno的影响。因此,新的UNIX和C实现已经改变为每个线程各自有自己对应的errno。例如,Linux将其定义为:
extern int *__errno_location(void);
#define errno (*__errno_location(void))
这样,每个线程便可通过内建的专门函数获得各自errno的存储单元。
头文件定义了变量errno以及它可以取值的错误码。每一种错误码有一个符号名,它们是定义在中以字母'E'开头的宏名字。所有错误码的值都是正整数且除了EWOULDBLOCK和EAGAIN之外每一个都互不相同。因此,可以用它们作为switch语句内的case标号来区分错误情形。表1-1列出了一些常见的错误码。
程序开始时,变量errno的初值一定是0,UNIX的系统调用和许多库函数当遇到错误时均保证设置该变量为某个非0值。errno的值只有在函数调用出错时才被设置,函数调用成功则不会改变,它可能是前一次调用某个函数出错时的值。因此,不应当用errno来检测一个调用是否失败。正确的做法是仅当函数的返回值指出这个变量被明显设置时才用errno来确定错误原因。
不会有任何函数设置errno的值为0来指出错误,errno的这个性质可以用来检测某些特殊函数的错误返回值。有少数函数,如1.8.3节介绍的pathconf(),在出现错误的情况下仍返回一个合法值,但同时也设置errno。对于这些函数,如果我们想检测是否遇到了错误,应当在调用它们之前置errno为0,函数调用之后再检查其值。我们在程序1-3中会见到这种情况的例子。
errno的错误码是整数,C标准函数strerror()可将错误码转换为可读的报错信息。
#include <string.h>
char *strerror(int errnum);
strerror()返回与errnum错误码相对应的错误信息字符串。我们不能修改由strerror()返回的字符串,同样,如果紧接着再次调用strerror(),前一次调用得到的字符串将被覆盖。
如果想直接将errno当前值对应的错误信息输出到标准报错文件,则可调用perror()。
#include <stdio.h>
void perror(const char *msg);
perror()首先打印msg指定的信息,后随一个冒号和空格,然后打印与errno对应的错误字符串。
如果msg是空指针或指向空字符串,perror()打印的将是与strerror()完全相同的信息,但perror()附加有换行符,而strerror()没有。
例1-1 当系统调用或库函数发生错误时,在有些情况下会需要立即终止程序的运行,因为继续执行已没有意义。此时,可以用perror()或strerror()这两个函数之一打印出对应的错误信息,然后调用exit(1)终止程序的执行。本书的很多例子就是这种情况。为此,我们统一使用一个函数err_exit()来完成报告错误并退出的动作。这个函数以宏方式定义在我们的头文件err_exit.h(程序1-1)中,其中,宏调用参数MESSAGE给出用户需要输出的信息。
本书在介绍每一个函数时,有时会省略返回值的说明,此时除非特殊说明,均遵循UNIX的一般约定:调用成功的返回值是0,错误返回值是–1,并设置了错误码于errno中。