我们知道C语言诞生于UNIX之下。UNIX系统为了清晰和简单而定义了新的标准。UNIX的每个实现对错误的系统调用都采用一种简单的指示方法。C语言继承了这种思想。C可调用的函数不能做到的是准确地报告发生了什么错误,你从返回值能得到的所有信息就是是否有错误发生。必须从其他地方来获得细节。
数学错误:我们一般把数学错误分为,向上溢出(数值太大而不能作为指定类型表示的时候就会发生向上溢出)、向下溢出(数值太小而不能作为指定类型表示的时候就会发生向下溢出)、域错误(当接受一个指定的参数值而产生的结果没有被定义的时候就会发生域错误)。这里没有提到有效值丢失的问题,这个是个不确定是否要报告的错误,但是对于C程序员来说,这个不是个需要报告的错误。
有许多其他头文件定义了一些错误存储在errno中。math.h中声明的很多函数都将errno.h中声明的宏EDOM和ERANGE的值存储在errno中。stdlib.h中声明的一些函数把文本串转换为各类算数类型的值。这些函数的部分或者全部将ERANGE存储在errno中。stdio.h中声明的一些函数改变文件中下一次读或者写操作的位置。这些函数会在errno中存储一个正值,一般选择宏名EFPOS代表那个正值。signal.h中声明的函数signal,也在errno中存储一个正值。注意如下的C标准关于errno地说明:errno展开为一个可以修改的int类型的左值,它的值被某几个库函数设置为一个正的错误编号(注意,宏errno不一定是一个对象的标识符,它也可能展开为一个函数调用返回的,可以修改的左值,比如*errno())。还有一点值得注意,errno的值在程序启动时为0,并且它绝对不会被任何库函数设置为0。因此一个程序如果要使用errno进行错误检查,应该在一个库函数调用之前把它设置为0,然后在下一个库函数调用之前查看errno的值,如果不及时查看,就有可能被下一个库函数改写errno的值。
errno.h头文件:
#ifndef MY_ERRNO_H
#define MY_ERRNO_H
#ifndef MY_YVALS_H /**保护宏*/
#include "my_yvals.h"
#endif // MY_YVALS_H
/**错误编码*/
#define EDOM _EDOM
#define ERANGE _ERANGE
#define EFPOS _EFPOS
/**可以在这添加自己定义的错误编码*/
#define _NERR _ERRMAX
/**声明*/
extern int errno;
#endif // MY_ERRNO_H
注意为什么要将#ifndef MY_YVALS_H
在在”my_yvals.h”外面。因为几个标准头文件包含这个头文件时,它非常有可能在一个翻译单元中被多次请求。一旦”my_yvals.h”编程翻译单元的一部分,宏保护就会跳过#include
预处理命令。这个头文件就不会被重复读入。
关于<errno.h>
的测试代码:
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main()
{
assert(0 == errno);
perror("No error reported as");
errno = ERANGE;
assert(ERANGE == errno);
perror("Range error reported as");
errno = 0;
assert(0 == errno);
sqrt(-1.0);
assert(EDOM == errno);
perror("Domain error reported as");
puts("SUCCESS testing <errno.h>");
return 0;
}
如上面代码所示,每次调用一个库函数之前一定要将errno设为0,调用一个库函数完毕后,应该马上检查errno的值。