第二十四章 错误处理
assert.h: 诊断
void assert(int expression);
assert声明在assert.h中,实际上是一个宏。其参数必须是一种“断言”,即被认为正常情况下一定为真的表达式。
每次执行assert时,判断此断言,若为假(0),则显示一条错误信息,并调用abort函数终止程序执行。
这条错误信息包含了:断言(以文本格式)、包含assert调用的文件名、assert调用所在的行号。
禁止assert调用
方法是:在包含assert.h之前,定义宏NDEBUG。如:
#define NDEBUG
#include <assert.h>
errno.h: 错误
标准库中的一些函数通过向errno.h中声明的errno变量存储一个错误代码来表示有错误发生。
大部分使用errno变量的函数集中在math.h,但也有一些在标准库的其他部分。
如果errno不为0,则说明函数调用过程中有错误发生。
errno在程序开始的时候值为0,通常在调用函数前把errno置为0,库函数不会将errno清零,这是程序员的责任。
用法如:
y = sqrt(x); // x为负数则出错
if (errno != 0) {
fprintf(stderr, "sqrt error, terminated. \n");
exit(EXIT_FAILURE);
}
perror函数和strerror函数
void perror(const char *s);
char *strerror(int errnum);
当库函数向errno存储了一个非零值时,通过perror函数和strerror函数可以得到描述这种错误的信息。
perror函数声明在stdio.h中,它会按照如下顺序把错误信息输出到stderr:
调用perror的参数: 出错消息(内容根据errno的值决定)
strerror函数声明在string.h中,它传入errno,返回一个指针,指向描述出错消息的字符串。
signal.h: 信号处理
signal.h提供了处理异常的工具,即信号(signal)。信号有两种类型:运行时错误(例如除以0),程序以外导致的事件(例如用户输入了ctrl+c)。
当有错误或外部事件发生时,我们称产生了一个信号。大多数信号是异步的:它们可以在程序执行过程中的任意时刻发生。
信号宏
signal.h定义了一系列宏,用于表示不同的信号。参见书本。
signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数安装一个信号处理函数。第一个参数是信号的代码,第二个参数是一个指向信号处理函数的指针。
一旦随后在程序执行过程中出现了对应的信号,信号处理函数就会被自动调用,信号的代码作为参数传递给信号处理函数。
除非信号是由调用abort函数或raise函数引发的,否则信号处理函数不应该调用任何库函数,或者试图使用一个静态存储期限的变量。
一旦信号处理函数返回,程序会从信号发生点恢复并继续执行。但是,如果信号是SIGABRT,当处理函数返回时程序会异常终止。如果信号是SIGFPE,那么处理函数返回的结果是UB(即不要使用它)。
signal函数的返回值是指定信号的前一个处理函数的指针。
预定义的信号处理函数
signal.h提供了两个预定义的信号处理函数(都用宏表示):
-
SIG_DFL。表示按默认方式处理信号,大多数情况下会导致程序终止。
-
SIG_IGN,指明随后当信号SIGINT发生时,忽略该信号。
当程序刚开始执行时,根据不同的实现,每个信号的处理函数都会被初始化为SIG_DFL和SIG_IGN。
如果signal调用失败(即无法对指定信号安装处理函数),就返回SIG_ERR(不是一个函数),并在errno中存入错误代码。
C语言要求,除了SIGKILL以外,当一个信号的处理函数被调用时,该信号的处理函数要被重置为SIG_DFL,或者以其他方式加以封锁。
raise函数
int raise(int sig);
通常信号都是自然产生的,但也可以通过raise函数触发。
raise函数的返回值可以用来测试调用是否成功:0代表成功,非0代表失败。
setjmp.h: 非局部跳转
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
通常情况下,函数会返回到它被调用的位置。setjmp.h可以使一个函数直接跳转到另一个函数,而不需要返回。
setjmp宏“标记”程序中的一个位置,随后可以用longjmp函数跳转到该位置。这一机制主要用于错误处理。
setjmp宏会将当前“环境”保存到一个jmp_buf类型的变量中,然后返回0。如果要返回setjmp宏所标记的位置,可以使用longjmp函数,调用的参数是调用setjmp宏时使用的同一个jmp_buf类型的变量。longjmp函数首先会根据jmp_buf变量的内容恢复当前环境,然后从setjmp宏调用中返回。这次setjmp宏的返回值是val,即调用longjmp函数时的第二个参数(如果val是0,那么返回1)。
如果longjmp的参数未被setjmp初始化,调用longjmp的结果是UB。