跳转至

第二十四章 错误处理


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。