处理来自系统调用和库函数调用的错误
几乎每个系统调用和库函数都会返回某类状态值,用以表明调用成功与否。要了解调用是否成功,必须坚持对状态值进行检查。若调用失败,那么必须采取相应行动。至少,程序应该显示错误消息,警示有意想不到的事件发生。
不检查状态值,少敲儿个字,听起来的确诱人(尤其是见识到了不检查状态值的UNIX/Linux 程序以后),但实际却得不偿失。认定系统调用或库函数“不可能失败“,不对状态返回值进行检查,这会浪费掉大把的程序调试时间。
少数几个系统函数在调用时从不失败。例如, getpid()
总能成功返回进程的ID, 而_exit()
总能终止进程。无需对此类系统调用的返回值进行检查。
处理系统调用错误
每个系统调用的手册页记录有调用可能的返回值,并指出了哪些值表示错误。通常,返回值为-1
表示出错。因此,可使用下列代码对系统调用进行检查:
fd = open(pathname, flags, mode);
if(fd == -1){
//code to handle the error.
}
if(clode(fd) == -1){
//code to handle the error.
}
系统调用失败时,会将全局整形变量errno
设置为一个正值,以标识具体的错误。程序应包含<errno.h>
头文件,该文件提供了对errno
的声明,以及一组针对各种错误编号而定义的常量。所有这些符号名都以字母E
打头在每个手册页内标题为ERRORS
的章节内,都刊载有一份相应系统调用可能返回的errno
值列表。以下便是利用errno
诊断系统调用错误的一个简单示例:
cnt = read(fd, buf, numbytes);
if(cnt == -1){
if(errno == EINTR)
fprintf(stderr, "read was interruped by a signal\n");
else{
//some other error occured.
}
}
如果调用系统调用和库函数成功, errno
绝不会被重置为0, 故此,该变量值不为0, 可能是之前调用失败造成的。此外,SUSv3
允许在函数调用成功时,将errno
设置为非零值(当然,几乎没有函数会这么做)。因此,在进行错误检查时,必须坚持首先检查函数的返回值是否表明调用出错,然后再检查errno
确定错误原因。
少数系统调用(比如,getpriority()
) 在调用成功后,也会返回-1
。要判断此类系统调用是否发生错误应在调用前将errno
置为0
, 并在调用后对其进行检查(上述手法同样适用于某些库函数)。
系统调用失败后,常见的做法之一是根据errno
值打印错误消息。提供库函数perror()
和strerror()
, 就是出于这一目的。
fd = open(pathname, flags, mode);
if(fd == -1){
perror("open");
exit(EXIT_FAILURE);
}
函数strerror()
会针对其errnum
参数中所给定的错误号,返回相应的错误字符串。
#include <string.h>
char* strerror(int errnum);
处理来自库函数的错误
不同的库函数在调用发生错误时,返回的数据类型和值也各不相同。(参见每个函数的手册页。) 从错误处理的角度来说,可将库函数划分为以下几类
-
某些库函数返回错误信息的方式与系统调用完全相同返回值为
-1
, 伴之以errno
号来表示具体错误remove()
便是其中一例,可使用该函数来删除文件(调用unlink()
系统调用)或目录(调用rmdir()
系统调用) 。对此类函数所发生的错误进行诊断,其方式与系统调用完全相同。 -
某些库函数在出错时会返回
- 1
之外的其他值,但仍会设置errno
来表明具体的出错情况。例如,fopen()
在出错时会返回一个NULL
指针,还会根据出错的具体底层系统调用来设置errno
。函数perror()
和strerror()
都可用来诊断此类错误。 -
还有些函数根本不使用
errno
。对此类函数来说,确定错误存在与否及其起因的方法各不相同,可见诸千相应函数的手册页中,不应使用errno
、perror()
或strerror()
来诊断错误。