怎么说 断言assert
assert宏的原型定义在
#include<assert.h>
void assert( int expression );
程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Realease版本发行给用户使用。debug版本中含有调试信息,不会对程序进行优化,assert相应的宏会被执行。
release版本中不含有调试信息,会对程序进行优化,assert相应的宏不会被执行。 断言assert是仅在Debug版本起作用的宏,它用于检查“不应该发生(即在你的主观意识里断言里的表达式是肯定成立的)”的情况。以下所有的说法都是对应Debug版本来论述assert();
当使用assert的时候,给它个参数(即一个判读为真的表达式)。此时预处理器产生测试该断言的的代码,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。 在调试结束后,可以通过在包含#include
#undef assert /* remove existing definition */
/*如果当前宏assert的定义不存在,那么开头的#undef预处理指令也没有任何副作用。总是可以#undef一个名字,不论他是不是被定义为一个宏。(可以把这作为良性取消定义考虑)但是如果定义可能改变的话,这条预处理指令还是很有必要的*/
#ifdef NDEBUG
#define assert (test) ((void) 0) /* passive form */
#else
#define assert (test) ... /* active form*/
#endif
一个简单地编写宏的活动形式的方式是:
#define assert (test) if(!(test)) \
fprintf (stderr , "Assertion failed: %s,file %s,line %i\n", \
#test, _FILE_, _LINE_) /* UNACCEPTABLE */
这种方式因为各种各样的原因而不能接受:
1. 宏不能直接调用库的任何输出函数,例如fprintf。它也不能引用宏stderr。这些名字只能在头文件<stdio.h>中正确的声明或者定义。程序可能没有包含这个头文件,单<assert.hh>一定不能包含。一个程序如果不包含某个头文件,就可以定义宏来对该头文件的任意函数名重新命名。这就要求这个宏必须调用一个具有隐藏名字的函数来进行实际的输出。
2. 宏必须能扩展为一个*void*类型的表达式。例如程序能包含一个形如(assert(0< x), x < y ) 的表达式,这就不能使用*if*语句了。任何测试都要在一个表达式内部使用某个条件操作符。
3. 宏应该可以扩展为有效并且紧凑的代码。否则,程序员就会尽量避免使用断言。而这个版本却总是调用一个传递了5个参数的函数。
assert.h
/* assert.h standhard header */
#undef assert /* remove existing definition */
#ifdef NDEBUG
#define assert(test) ((void)0)
#else /* NDEBUG not define */
void _Assert(char *);
/* macros */
#define _STR(x) _VAL(x)
#define _VAL(x) #x
#define assert(test) ((test) ? (void) 0 \
: _Assert (_FILE_":"_STR(_LINE_)"" #test))
#endif
这段代码显示了文件assert.h。宏assert的这种实现履行了测试方针。通过这种方式,一个优化的翻译器大都能清楚明显正确的断言的所有代码。
内置宏 _LINE_ 没有扩展成字符串面量,他变成一个十进制常量。把他转换为适当的形式需要一个额外的处理层。那要通过向头文件中添加两个隐藏的宏_STR 和 _VAL来实现。其中一个宏用他的十进制常量扩展来取代 _LINE_ ,另一个是把十进制常量转化为一个字符串字面量。忽略_STR 和 _VAL中的任何一个,就会得到字符串字面量“_LINE_ ”,而不是你想要的结果。
xassert.c
/* _Assert function */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
void _Assert(char *mesg)
{
/* print assertion message and abort */
fputs(mesg, stderr);
fputs(" -- assertion failed\n",stderr);
abort();
}
文件xassert.c定义了宏定义的隐藏库函数_Assert。_Assert函数能解析诊断信息,也能补充缺少位数。
**前向引用**
函数_Assert使用了两个其他的库函数。他通过调用<stdio.h>中声明的函数fputs把字符串写到标准错误流,通过调用<stdlib.h>中声明的函数abort()异常终止程序的执行。
预处理器(preprocessor)
预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代。预处理器可由语言(如C)要求或以后作为提供额外功能(诸如为FORTRAN提供Ratfor预处理器)的附加软件。
我们先编写一段代码简单的用一下assert():
#include <stdio.h>
//#undef NDEBUG /*打开断言, 一般情况下不需要加这行*/
//#define NDEBUG /*关闭断言,如果你认为断言没有存在的必要就加上这一 行*/
#include<assert.h>
#include<stdlib.h>
int main()
{
int a = 3 ;
assert( a == 3); /*使用断言 假定 a != 3 便终止进程*/
printf("a=%d\n",a);
return 0;
}
<assert.h>唯一的目的就是提供宏assert的定义。
在#include<assert.h>之前定义NDEBUG,头文件就会把这个宏定义为不执行任何操作的静止形式(如果你认为断言没有存在的必要,则只需在<assert.h>之前定义NDEBUG)。
.......(此后省略100字详解,waiting.......)
断言为什么会被这样定义呢?
在实战中程序的异常终止程序绝非明智之举。无论相伴而产生的提示信息对程序员多么有用,他对用户都是天书,某种形式的错误恢复才应该是首选的方案,任何诊断信息都应该能被用户理解。
我们需要是这样一种方式,断言只在调试程序的时候起作用。这样从一开始就可以记录下所需要的断言,让他们帮助尽早发现那些最糟的逻辑错误,稍后,可以加些代码来从执行程序时发生的错误中恢复。我们希望这些断言作为文档保留下来,同时又不希望他们产生代码。