防御性编程的关键点是针对程序中的一些意外错误进行防御。这是提高软件质量的一种辅助方法。断言用于防御性编程,我们总是会做出一些假设,而断言就是用来在代码中捕获这些假设的。使用断言是为了验证预期的结果——当程序执行到断言的位置时,对应的断言应该为真;如果断言不正确,程序将终止执行并给出错误信息。断言验证可以随时启用和禁用,因此可以在程序调试时启用断言,在程序发布时禁用断言。同样,在程序上线后,如果最终用户遇到问题,他们可以重新启用断言。1.原型函数在大多数编译器下,assert()是一个宏;在一些编译器下,assert()是一个函数。我们不需要关心这些差异,我们可以将assert()作为一个函数来使用。即:voidassert(intexpression);程序运行时会计算括号中的表达式。如果expression不为0,则表示其值为真。assert()不执行任何动作,程序继续执行后面的语句;ifexpression如果为0,则其值为false,assert()将报错并终止程序的执行。值得知道的是,程序的终止是调用abort()函数。该函数的作用是终止程序的执行,直接从调用处跳出,abort()函数也是一个标准库函数,定义在中。所以用assert()来判断程序中是否存在明显的非法逻辑,如果出现则终止程序,避免造成严重的后果,同时也方便查找错误。2.详细定义assert()定义在c标准库的中。让我们看看assert.h中的定义:#ifdefNDEBUG#defineassert(e)((void)0)#else#defineassert(e)((void)((e)?((void)0):__assert(#e,__FILE__,__LINE__)))#endif可以看到,NDEBUG定义时,assert()无效,只有NDEBUG未定义时,assert()才实现具体功能。NDEBUG的意思是“NoDebug”,即“非调试”。程序一般分为Debug版和Release版。Debug版本是程序员在测试代码时使用的编译版本,Release版本是程序提供给用户时使用的发布版本。一般来说,assert()只有在Debug版本才有。功能宏。在发布版本时,我们不应该再依赖assert()宏,因为一旦程序出错,assert()会抛出用户无法理解的提示信息,并在没有警告的情况下终止程序执行,这将严重影响软件。用户体验,所以assert()应该在发布模式下失效。另外,在程序中频繁调用assert()会影响程序的性能,增加额外的开销。因此,可以在中定义NDEBUG宏来关闭assert()函数。#defineNDEBUG//定义NDEBUG#ifdefNDEBUG#defineassert(e)((void)0)#else#defineassert(e)((void)((e)?((void)0):__assert(#e,__FILE__,__LINE__)))#endifNDEBUG定义时:NDEBUG定义时,assert()执行的具体函数变为((void)0),表示什么都不做。this在宏中使用的目的是为了防止这个宏被用作右值,因为void类型不能用作右值。所以当头文件中定义了NDEBUG时,assert()的检测功能就会自动失效。未定义NDEBUG时:可以看到assert()的执行实际上是使用了三元运算符来判断表达式e的真假,并进行相应的处理。当表达式e为真时,执行(void)0,即什么都不执行,程序继续运行;当表达式e为false时,会打印出assert的内容,当前文件名,当前行号,然后程序执行终止。3.使用示例当NDEBUG没有定义,assert()函数生效时,我们来看一个简单的assert()使用示例:#include#includevoidmain(){int我=8;断言(我>0);printf("i=%d\n",i);我=-8;断言(我>0);printf("i=%d\n",i);}可见程序中使用assert(i>0)进行判断;当i>0时,assert的判断表达式为真,assert无效;当i<0时,assert的判断表达式为false,assert生效。程序第5行i=8,执行完assert后,程序会执行后续的printf打印出i的值;而第8行i=-8,执行完assert后,程序会终止,不会执行后面的printf。4、使用注意事项使用assert的核心原则是:它是用来处理不应该发生的情况的,这也是为什么要在Debug版本的程序中使用它的原因。这是为了消除在程序的Debug版本中主观上不应该出现的错误。应该解决,这样这种不应该出现的错误在程序的Release版本中才不会出现。与ifassert不同的是,使用了一个函数来判断表达式条件是否满足,然后终止程序。在Debug版本中,assert用于判断程序的合法性,定位不允许出现的错误。那什么是不应该出现的错误呢,比如下面这种从主观上来说,这种被0除是不应该出现的。需要在Debug版本中检查并排除该错误,以免影响后续程序的执行。#include#includevoidfun(inta,intb){assert(b!=0);inti=a/b;}if是关键字,一般用accordingtoConditions来判断逻辑的正确性,即是否按条件执行,Debug版本和Release版本都可以使用。比如下面使用if时,允许这些判断条件正常发生,这是合理的,需要根据发生的条件执行相应的逻辑,程序才能往下执行。#include#includevoidfun(inta,intb){if(a>0)...elseif(a<0)...else...}所以在在使用之前,您可以先进行判断。如果逻辑上不允许发生,那么在Debug阶段使用assert解决问题;如果逻辑允许,则使用if。当然你也可以在if判断后使用条件返回操作。剔除无逻辑,本质是为了防止错误的逻辑影响后续程序的执行。比如上面例子中判断除以0的操作也可以用if:#include#includevoidfun(inta,intb){if(0==b)返回;inti=a/b;}用来判断函数的入参一般可以用assert来判断函数入参的合法性,比如入参值是否一致,指针是否为空:#include#includevoidfun1(inta){assert(a>0);...}voidfun2(int*p){assert(p!=NULL);...}不要使用影响正常逻辑的判断条件语句assert的判断条件语句必须是确定的。Debug版本中用于消除错误的条件逻辑不应影响Release版本中的正常逻辑。例如下面的例子,在Debug版本中,当i++达到>=100时,assert生效,程序终止;但是在Release版本中,assert()是无效的,因为需要添加NDEBUG宏。assert(i++<100)变为空操作(void)0;由于没有执行i++语句,while变成了无限循环。#include#includevoidmain(){inti=0;while(i<=110){断言(i++<100);printf("i=%d\n",i);}}不要使用多重判断语句通常一个断言只用一个判断语句来实现。如果在一个assert中使用多条判断语句,当出现错误时,你将不知道是哪条条件语句出错了。性能不直观。#include#includevoidfun1(inta,intb)//错误使用{assert(a>0&&b>5);...}voidfun2(inta,intb)//正确使用{assert(a>0);断言(b>5);...}更多技术内容和书籍资料,请关注公众号《明杰嵌入式》