C标准库<assert.h>实现

1.背景知识

头文件<assert.h>唯一的目的就是提供assert宏定义,可以在程序中关键的地方使用这个宏来进行断言。如果一处断言被证明非真,希望程序在标准错误流输出一条适当的提示信息,并使执行异常终止。

可以这样写代码:

#include<assert.h>
...
assert(0 <= i && i < sizeof(a) / sizeof(a[0]));

当然上面的代码不是实战中的最好的形式,程序异常终止应该改为某种错误的恢复。

宏NDEBUG

可以通过在程序的某些地方定义宏NDEBUG来改变assert的展开方式

如果程序某个包含assert的地方没有定义NDEBUG,该头文件就会将宏assert定义为活动形式,它就可以展开为一个表达式,测试断言并在
断言为假的时候输出一条错误信息,然后程序终止。反之,如果定义了NDEBUG,头文件就会把这个宏定义为不执行任何操作的静止形式。

2.<assert.h>的使用

从上面的代码中可以看到,可以使用一个简单的谓词来简化assert:

if(!ok)
    abort();  //在头文件<stdlib.h>中声明

如果觉得断言没有存在的必要,就在包含头文件之前加上下面的代码:

#define NDEBUG //取消断言
#include<assert.h>

可以在整个源文件中用不同的方式控制断言,当断言在频繁执行的循环内部发生时,性能可能会急剧下降,或在达到提示性的部分之前,一个更早的断言可能会终止程序。要打开断言,可以写:

#undef NDEBUG
#include<assert.h>

要关闭断言,可以写:

#define NDEBUG
#include<assert.h>

注意:即使宏NDEBUG已经被定义了,我们仍然可以安全地定义它,这是一个良性重定义

3.<assert.h>的实现

从上面的分析知该头文件的大致框架如下:

#undef assert  //消除已定义的
#ifdef NDEBUG
#define assert(expr)  ((void) 0)  //功能失效
#else
#define assert (expr) ...
#endif

一个简单的编写宏assert的活动形式的方式如下:

#define assert(expr) if(!(expr)) \
    fprintf(stderr, "Assertion failed: %s, file %s, line %i\n", \
        #expr, __FILE__, __LINE__)

这种方式因为如下几种原因不能接受:

1、宏不能直接调用库的任何输出函数

上面的定义中包含fprintf、stderr等在stdio.h中定义的函数或宏,程序可能没有包含这个头文件

2、宏必须能扩展为一个void类型的表达式

3、宏应该可以扩展为有效并且紧凑的代码

这个版本却总是调用了一个传递了5个参数的函数

修改后的assert宏如下:

#undef assert
#ifdef NDEBUG
    #define assert(expr)  ((void) 0)
#else
    void __bad_assertion (const char *_mess);
    #define    __str(x)    # x
    #define    __xstr(x)    __str(x)
    #define    assert(expr)    ((expr)? (void)0 : \
                __bad_assertion("Assertion \"" #expr \
                    "\" failed, file " __xstr(__FILE__) \
                    ", line " __xstr(__LINE__) "\n"))
#endif

其中__LINE__
是内置宏,代表该行代码的所在行号,由于__LINE__没有扩展成字符串字面量,它变成了一个十进制常量,把它转换成适当的形式需要一个额外的处理层。
向头文件中添加两个隐藏的宏__str和__xstr来实现,其中一个宏用它的十进制常量扩展来取代__LINE__,另一个是把十进制常量转换成一个字
符串字面量

宏调用的隐藏库函数__bad_assertion的实现:

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
void __bad_assertion(const char *mess) {
        fputs(mess, stderr);
        abort();
 }

函数__bad_assertion使用了两个其他的库函数,通过调用<stdio.h>中声明的函数fputs把字符串写到标准错误流,并使用abort异常终止程序的执行,有关这些相关头文件以后会详细剖析。

4.<assert.h>的测试

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
int main( void )
{
       FILE *fp;
       fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
       assert( fp );                           //所以这里不会出错
       fclose( fp );

       fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
       assert( fp );                           //所以这里出错
       fclose( fp );                           //程序永远都执行不到这里来
       return 0;
}

注意:

1.在函数开始处检验传入参数的合法性如:

int resetBufferSize(int nNewSize)
{
  //功能:改变缓冲区大小,
  //参数:nNewSize 缓冲区新长度
  //返回值:缓冲区当前长度
  //说明:保持原信息内容不变     nNewSize<=0表示清除缓冲区
  assert(nNewSize >= 0);
  assert(nNewSize <= MAX_BUFFER_SIZE);
  ...
}

2.每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败,如:

assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);//不好

//好
assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);

3.不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题,如:

错误:

assert(i++ < 100);

这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。

正确:

assert(i < 100);
i++;

4.assert和后面的语句应空一行,以形成逻辑和视觉上的一致感。

5.在有的地方,assert不能代替条件过滤。

时间: 2024-09-14 17:19:14

C标准库<assert.h>实现的相关文章

C标准库&lt;ctype.h&gt;实现

1.背景知识 ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数(C character classification functions),用于测试字符是否属于特定的字符类别,如字母字符.控制字符等 我们经常将字符排序并分成不同的类别,为了识别一个字母,可以编写: if('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z') ...... 当执行字符集是ASCII码的时候,可以得到正确的结果,但

C标准库&lt;string.h&gt;实现

1.背景知识 <string.h>中声明的函数是对标准C的一个重要补充,它们支持C语言把文本作为字符数组操作的传统. string.h是C语言中C标准库的头文件,其中包含了宏定义.常量以及函数和类型的声明,涉及的内容除了字符串处理之外,还包括大量的内存处理函数:因此,string.h这个命名是不恰当的.在string.h中定义的函数十分常用,作为C标准库的一部分,它们被强制要求可以在任何支持C语言的平台上运行.但是,部分函数存在一些安全隐患, 例如缓存溢出等,导致程序员宁愿使用一些更安全的函数

C标准库&lt;signal.h&gt;实现

背景知识 signal.h是C标准函数库中的信号处理部分, 定义了程序执行时如何处理不同的信号.信号用作进程间通信, 报告异常行为(如除零).用户的一些按键组合(如同时按下Ctrl与C键,产生信号SIGINT).信号是程序执行过程中发生的异常事件,同步信号的产生 是因为程序自身的某些动作,例如除零或不正当的访问存储器,异步信号是由程序外部的行为引起的,比如有人敲击了提示键或者另外一个程序(异步地执行)给你 的程序发信号,都会引发一个异步信号.程序不能屏蔽的信号要求立即得到处理.如果不对发生的信号

C标准库&amp;lt;assert.h&amp;gt;的实现详解_C 语言

本文实例讲解了C标准库<assert.h>的实现过程及相关用法.分享给大家供大家参考.具体分析如下: 一.背景知识 头文件<assert.h>唯一的目的就是提供assert宏定义,可以在程序中关键的地方使用这个宏来进行断言.如果一处断言被证明非真,希望程序在标准错误流输出一条适当的提示信息,并使执行异常终止. 可以这样写代码: #include<assert.h> ... assert(0 <= i && i < sizeof(a) / si

C标准库参考指南(2)ctype.h

2. ctype.h 字符类头文件用于测试字符以及转换字符.一个引用另一个字符的控制字符,是不属于可打印字符集的.在ASCII字符集中,0x0到0x1F的所有字符以及0x7F(删除键)是控制字符,可打印字符从0x20(空格)到0x7E(波浪号). 函数: isalnum(); isalpha(); iscntrl(); isdigit(); isgraph(); islower(); isprint(); ispunct(); isspace(); isupper(); isxdigit();

C标准库参考指南(1)assert.h

1.1 assert.h 断言头文件用于调试. 宏: assert(); 外部引用: NDEBUG 1.1. assert 声明: void assert(intexpression); 断言头文件中的宏允许你将一些特殊信息写入到标准错误文件. 如果表达式的值为0(false),那么表达式.源文件名和行号都会被发送给标准错误输出,并调用abort函数.如果标识符NDEBUG ("no debug")由#define NDEBUG定义,那么断言头文件中的宏就什么都不做. 标准错误输出的格

isdigit &amp;lt;ctype.h&amp;gt; &amp;lt;cctype&amp;gt;

原文:http://www.cplusplus.com/reference/clibrary/cctype/isdigit/ int isdigit ( int c ); 检查字符是否是十进制数字 检查参数c是否是一个十进制数字. 十进制数字包括以下几个数字: 0 1 2 3 4 5 6 7 8 9  想要得到不同的ctype函数在处理每个标准ANSII字符返回值的详细图表,请阅读参考<cctype>头文件. 在C++语言中,一个特定于语言环境模版版本的isdigit函数存在于头文件<l

isgraph &amp;lt;ctype.h&amp;gt; &amp;lt;cctype&amp;gt;

原文:http://www.cplusplus.com/reference/clibrary/cctype/isgraph/ int isgraph ( int c ); 检查一个字符是否是可显示字符. 检查参数c是否是一个可显示字符.可显示字符包括那些可以打印出来(由isprint返回值决定),不被认为是可显示字符的空格符(例如 ' ')除外. 想要得到不同的ctype函数在处理每个标准ANSII字符返回值的详细图表,请阅读参考<cctype>头文件. 在C++语言中,一个特定于语言环境模版

isupper &amp;lt;ctype.h&amp;gt; &amp;lt;cctype&amp;gt;

原文:http://www.cplusplus.com/reference/clibrary/cctype/isupper/ int isupper ( int c ); 检查一个字符是否是大写字母 检查参数c是否是一个大写字母 请注意哪些字符会被认为是大写字母可能依赖于当前正在使用的locale本地设置:在默认的c语言本地设置下,以下任何一个都是一个大写字母: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 想要得到不同的ctype函数在