C++技巧之断言Assert

 2244人阅读 评论(2) 收藏 举报
c++ struct 编程 64bit 编译器 linux

断言的应该是一种编程的常见技巧。我所应用的断言有两种,一种是动态断言,即大家所熟知的C标准库的assert()宏,一种是C++中的静态断言,即在编译期间检查。

 

1)动态断言:assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:

[c-sharp]  view plain copy
  1. #include <assert.h>  
  2. void assert( int expression );  

assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
大家要注意是,其中的表达式为假时,会终止程序运行,包括我在内经常会写错代码,断言一个指针是否为空,往往写成了
assert(!p);其实应该写成assert(p);
assert是运行期的判断,并且会强制终止程序,一般要求只能用于debug版本中,是为了尽可能快的发现问题。尤其在我所从事的电信软件产品中,assert是要从release版本中去掉。所以一般开发会重新定义assert宏。


2)静态断言,在新的C++标准中C++0x中,加了对静态断言的支持,引入了新的关键字static_assert来表示静态断言。使用静态断言,我们可以在程序的编译时期检测一些条件是否成立。但这个关键字太新了,没有几个编译器是支持的(好像VC2008支持,我用VC很少,主要是在linux下C++编程)。于是可以使用C++现有的模板特性来实现静态断言的功能。boost中也已有BOOST_STATIC_ASSERT宏的实现,有兴趣的同学可以down下来仔细研究一下,它的断言信息更丰富,下面为我的简单实现:

[c-sharp]  view plain copy
  1. // declare a tempalte class StaticAssert.  
  2. template <bool assertion> struct StaticAssert;  
  3.   
  4. // only partial specializate parameter's value is true.  
  5. template <> struct StaticAssert<true>   
  6. {  
  7.   enum { VALUE = 1 };  
  8. };  
  9.  
  10. #define STATIC_ASSERT(expression) (void)StaticAssert<expression>::VALUE   

原理是,先声明一个模板类,但后面仅仅偏特化参数值为true的类,而为false的类则一个未定义的类,即是一个未完整的类型,编译期间无法找到StaticAssert<false>::VALUE类型。举例如下:

[c-sharp]  view plain copy
  1. STATIC_ASSERT(4 == sizeof(long) ); //在 32bit机上OK  
  2. STATIC_ASSERT(4 == sizeof(long) ); //在 64bit机上NG,long为8字节  

静态断言在编译时进行处理,不会产生任何运行时刻空间和时间上的开销,这就使得它比assert宏具有更好的效率。另外比较重要的一个特性是如果断言失败,它会产生有意义且充分的诊断信息,帮助程序员快速解决问题


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

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

1.<asssert.h>

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

[cpp]  view plain copy
  1. #define assert(_exp) ( (void)0 )  

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

2.assert 的使用

一个断言可以简写为:

[cpp]  view plain copy
  1. if(!okay){  
  2.     abort();  
  3. }  

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

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

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

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

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

[cpp]  view plain copy
  1. /*able*/  
  2. #undef NDEBUG  
  3. #include <assert.h>  
  4.   
  5. /*disable*/  
  6. #define NDEBUG  
  7. #include <assert.h>  
这段代码中,可能会造成:对NDEBUG进行重定义或取消没有定义的NDEBUG宏,但他们是良性重定义和良性取消定义。

3.assert设计的注意事项

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

[cpp]  view plain copy
  1. #undef assert  
  2.   
  3. #ifdef NDEBUG  
  4. #define assert(_exp) ((void)0)  
  5. #else  
  6. #define assert(_exp) ...  
  7. #endif  
这又是一个良性取向定义,因为:总是可以#undef 一个名字,无论它是不是被定义为一个宏。

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

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

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

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

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

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

宏的定义:

[cpp]  view plain copy
  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  
注意在这,__LINE__是一个整数,在_STR(__LINE__)中用实际的数字来替代参数并调用_VAL(X) #X转换为字符串,因为嵌套宏展开顺序是:替换外层宏,传入实际参数替代形参,替换内层宏


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


[cpp]  view plain copy
  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. }  

5.VS2008的实现

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

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

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


你可能感兴趣的:(收藏,评论,举报)