assert宏的实现

在看《编写高质量的C语言代码》中第二章提到,使用断言,于是翻出以前看过的《C标准库》查看assert宏的实现。

这篇文章主要内容来自 《C标准库》

1.<asssert.h>

头文件<assert.h>中除了定义宏assert以外还引用了另外一个宏 NDEBUG,后者不是定义在<assert.h>中的。如果NDEBUG出现在任何包含<assert.h>的文件中,并被定义为宏名,那么宏assert就直接被定义为:

  1. #define assert(_exp) ( (void)0 )  
#define assert(_exp) ( (void)0 )

宏assert应该作为一个宏,而不是一个实际的函数来实现。

2.assert 的使用

一个断言可以简写为:

  1. if(!okay){  
  2.     abort();  
  3. }  
if(!okay){
	abort();
}

应该记住assert宏仅仅是帮助记住源代码中作的假设,一个产品程序不能这样突然间终止,无论断言在调试的时候是多么的方便,最终他们都是多余的。

怎样控制宏的展开方式不过是一个风格问题,但是必须通过某种方式来控制DDEBUG是否定义。其中一种方式是修改源代码,如果你认为没有存在的必要了,则只需要在包含<assert.h>的源文件前面加上下面代码就可以了

  1. #define NDEBUG  /* disable assertions */   
  2. #include <assert.h>  
#define NDEBUG  /* disable assertions */
#include <assert.h>
这段代码清楚的说明了断言从此不再起作用,而当退回来重新进行到调试时,这种风格的缺陷也就爆出来了:此时必须重新编辑源代码。
普遍的编译器支持一种更为灵活的方法,它们允许在所有源文件之外定义宏,可以在make文件中定义/不定义来解决。

我们可以在源文件不同地方用不同的方式来控制断言,所以需要我们在一个源文件中各个不同地方打开或者关闭断言。

打开/关闭 断言可以写成:

  1. /*able*/  
  2. #undef NDEBUG   
  3. #include <assert.h>   
  4.   
  5. /*disable*/  
  6. #define NDEBUG   
  7. #include <assert.h>  
/*able*/
#undef NDEBUG
#include <assert.h>

/*disable*/
#define NDEBUG
#include <assert.h>
这段代码中,可能会造成:对NDEBUG进行重定义或取消没有定义的NDEBUG宏,但他们是良性重定义和良性取消定义。

3.assert设计的注意事项

为了对NDEBUG作出响应,所以该文件有一个总体结构

  1. #undef assert   
  2.   
  3. #ifdef NDEBUG   
  4. #define assert(_exp) ((void)0)   
  5. #else   
  6. #define assert(_exp) ...   
  7. #endif  
#undef assert

#ifdef NDEBUG
#define assert(_exp) ((void)0)
#else
#define assert(_exp) ...
#endif
这又是一个良性取向定义,因为:总是可以#undef 一个名字,无论它是不是被定义为一个宏。

一个简单的编写宏的活动形式的方式是:

  1. #define assert(_exp) if((!_exp)) \   
  2.     fprintf(stderr,"Assertion failed:%s,file %s,line %i\n",#test,\  
  3.     __FILE__,__LINE__) /*unacceptable*/  
#define assert(_exp) if((!_exp)) \
	fprintf(stderr,"Assertion failed:%s,file %s,line %i\n",#test,\
	__FILE__,__LINE__) /*unacceptable*/
但这段代码是不可接受的,因为:

(1)宏不能直接调用库的任何函数,例如fprintf,也不能引用宏stderr。因为这些名字都是在<stdio.h>中或其他文件中正确声明或者定义的,程序可能没有包含这些文件,但<assert.h>中一定不能包含这些头文件。一个程序如果不包含任何头文件,就可以定义宏来对该头文件中任意名字重命名。这就要求宏必须调用一个具有隐藏名字的函数来进行实际输出。

(2)宏必须能扩展为一个 void 类型的表达式。例如程序中,包含形如 (assert(0<x),x<y)的表达式,这就不能使用if语句了,任何条件测试都要在一个表达式内部使用某个条件操作符。

(3)宏应该可以扩展为有效并紧凑的代码,否则程序员会尽量避免使用这个宏。而这个版本却使用了传5个参数的函数。

4.assert的实现----《C标准库》

宏的定义:

  1. /*assert.h*/  
  2. /*<<C标准库>>*/  
  3.   
  4. #undef assert   
  5.   
  6. #ifdef NDEBUG   
  7.     #define assert(test) ((void)0)   
  8. #else   
  9.     void _Assert(char *);  
  10.     #define _STR(X) _VAL(X)   
  11.     #define _VAL(X) #X   
  12.     #define assert(test) ( (test)? (void)0\   
  13.     :_Assert(__FILE__":"_STR(__LINE__)" " #test) )  
  14. #endif  
/*assert.h*/
/*<<C标准库>>*/

#undef assert

#ifdef NDEBUG
	#define assert(test) ((void)0)
#else
	void _Assert(char *);
	#define _STR(X) _VAL(X)
	#define _VAL(X) #X
	#define assert(test) ( (test)? (void)0\
	:_Assert(__FILE__":"_STR(__LINE__)" " #test) )
#endif
注意在这,__LINE__是一个整数,在_STR(__LINE__)中用实际的数字来替代参数并调用_VAL(X) #X转换为字符串,因为嵌套宏展开顺序是:替换外层宏,传入实际参数替代形参,替换内层宏


_Assert(char *)这个隐藏的函数的实现:


  1. /*_Assert funcion*/  
  2. #include <assert.h>   
  3. #include <stdio.h>   
  4. #include <stdlib.h>   
  5.   
  6. void _Assert(char *mesg){  
  7.         fputs(mesg,stderr);  
  8.         fputs("----------assertion failed \n",stderr);  
  9.         abort();  
  10. }  
/*_Assert funcion*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

void _Assert(char *mesg){
		fputs(mesg,stderr);
		fputs("----------assertion failed \n",stderr);
		abort();
}

5.VS2008的实现

查看了Microsoft Visual Studio 9.0\VC\crt\src中assert.h中的实现

  1. #define assert(_Expression) (void)( (!!(_Expression)) \   
  2. || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )  
#define assert(_Expression) (void)( (!!(_Expression)) \
|| (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
使用:||运算的截断 和  !!(_Expression)两次取否,这两点用得太精辟了。

很明显,也使用了隐藏函数_wassert()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值