首页 > 编程语言 >防御式编程之断言assert的使用

防御式编程之断言assert的使用

时间:2022-11-24 12:06:13浏览次数:35  
标签:断言 int NDEBUG void 编程 程序 assert include

防御式编程的重点就是需要防御一些程序未曾预料的错误,这是一种提高软件质量的辅助性方法,断言assert就用于防御式编程,编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。使用断言是为了验证预期的结果——当程序执行到断言的位置时,对应的断言应该为真;若断言不为真时,程序会终止执行,并给出错误信息。可以在任何时候启用和禁用断言验证,因此可以在程序调试时启用断言而在程序发布时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。


1、原型函数

在大部分编译器下,assert() 是一个宏;在少数的编译器下,assert() 就是一个函数。我们不需要关心这些差异,可以只把 assert()当作函数使用即可。即:

void assert(int expression);

在程序运行时它会计算括号内的表达式,如果 expression为非0说明其值为真,assert()不执行任何动作,程序继续执行后面的语句;如果 expression为0说明其值为假,assert()将会报告错误,并终止程序的执行,值得了解的是,程序终止是调用abort()函数,这个函数功能就是终止程序执行,直接从调用的地方跳出,abort()函数也是标准库函数,在<stdlib.h>中定义。因此assert()用来判断程序中是否出现了明显非法的逻辑,如果出现了就终止程序以免导致严重后果,同时也便于查找错误。


2、详细释义

assert() 在c标准库中的<assert.h>中被定义。下面就看下在assert.h中的定义:

#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif

可以看到在定义了NDEBUG时,assert()无效,只有在未定义NDEBUG时,assert()才实现具体的函数功能。NDEBUG是“No Debug”的意思,也即“非调试”。程序一般分为Debug版本和Release版本,Debug版本是程序员在测试代码期间使用的编译版本,Release版本是将程序提供给用户时使用的发布版本,一般来说断言assert()是仅在Debug版本起作用的宏。在发布版本时,我们不应该再依赖assert()宏,因为程序一旦出错,assert()会抛出一段用户看不懂的提示信息,并毫无预警地终止程序执行,这样会严重影响软件的用户体验,所以在发布模式下应该让assert()失效,另外在程序中频繁的调用assert()会影响程序的性能,增加额外的开销。因此可以在<assert.h>中定义NDEBUG宏,将assert()功能关闭。

#define NDEBUG  //定义NDEBUG  
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif

  • 定义NDEBUG时:

当定义了NDEBUG之后,assert()执行的具体函数就变成了 ((void)0),这表示啥也不干了,宏里面这样用的目的是防止该宏被用作右值,因为void类型不能用作右值。所以当在头文件中定义了NDEBUG之后,assert()的检测功能就自动失效了。

  • 未定义NDEBUG时:

可以看到assert()执行实际上是通过三目运算符来判断表达式e的真假,执行相应的处理。当表达式e为真时,执行(void)0,即什么也不执行,程序继续运行;当表达式e为假时,那么它会打印出来assert的内容、当前的文件名、当前行号,接着终止程序执行。


3、用法举例

在未定义NDEBUG时,assert()功能生效的情况下,来看一个简单的assert()使用的例子:

#include <stdio.h>
#include <assert.h>
void main()
{
int i = 8;
assert(i > 0);
printf("i = %d\n", i);
i = -8;
assert(i > 0);
printf("i = %d\n", i);
}

可以看出在程序中使用assert(i > 0)来判断;当 i > 0 时,assert的判断表达式为真,assert不生效;当 i < 0 时,assert的判断表达式为假,assert生效。

在程序第5行 i = 8,执行完assert后,程序将执行后续的printf打印出 i 的值;而在第8行 i = -8,执行完assert后,程序终止,不会执行后续的printf。


4、使用注意事项

使用assert的核心原则是:用于处理绝不应该发生的情况,这就是为什么应该在程序Debug版本中使用,这是为了将主观上不应该发生的错误在程序Debug版本中就应该解决掉,从而在程序Release版本时不会产生这种不应该发生的类型的错误。

  • 和if的区别

assert用函数来判断是否满足表达式条件后终止程序,在Debug版本中用assert来判断程序的合法性,定位不允许发生的错误,那么什么是不应该发生的错误,例如像下面这种除0操作,主观上就不应该发生,就是就要在Debug版本中检查排除掉这种错误,以免影响后续程序的执行。

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{
assert(b != 0);
int i = a / b;
}

if是一个关键字,一般用于根据条件来判断逻辑的正确性,即是否根据条件对应执行,Debug和Release版本中都可以使用,例如下面用if的时候,就允许这些判断条件是正常发生的,是合理的,需要根据发生的条件执行对应的逻辑,程序可以往下执行。

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{
if(a > 0)
...
else if(a < 0)
...
else
...
}

因此在使用前,可以先判断下,如果逻辑不允许发生,那么就使用assert在Debug阶段将问题解决掉;如果逻辑允许的,那么就使用if,当然也可以用if判断后进行条件的return操作,来杜绝不允许逻辑,本质是防止错误的逻辑影响后续程序的执行。例如上述的用来判断除0操作的例子也可以用if:

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{
if(0 == b)
return;
int i = a / b;
}

  • 用于判断函数的入参

一般assert可以用于判断函数入参的合法性,比如入参值是否符合,指针是否为空:

#include <stdio.h>
#include <assert.h>
void fun1(int a)
{
assert(a > 0);
...
}
void fun2(int *p)
{
assert(p != NULL);
...
}

  • 不要使用影响正常逻辑的判断条件语句

assert的判断条件语句一定是确定的,在Debug版本中使用的排除掉错误的条件逻辑,不要影响到Release版本时的正常逻辑。例如下面的例子,在Debug版本时,i++到>=100时,assert生效,程序终止;但是到了Release版本,由于要增加NDEBUG宏,assert()无效。assert(i++ < 100)就变成了空操作(void)0;由于没有i++语句执行,那么while成了死循环。

#include <stdio.h>
#include <assert.h>
void main()
{
int i = 0;
while(i <= 110)
{
assert(i++ < 100);
printf("i = %d\n",i);
}
}

  • 不要用多个判断条件语句

一般一个assert只用一个判断语句来实现,如果在一个assert中使用多条判断语句,当错误发生时,会不知道是哪个条件语句出现错误,错误表现的就不直观。

#include <stdio.h>
#include <assert.h>
void fun1(int a, int b) //错误使用
{
assert(a > 0 && b > 5);
...
}
void fun2(int a, int b) //正确使用
{
assert(a > 0);
assert(b > 5);
...
}



标签:断言,int,NDEBUG,void,编程,程序,assert,include
From: https://blog.51cto.com/Sharemaker/5878545

相关文章

  • 3 c++编程-提高篇-模版
    ​ 重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!  生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉......
  • 编程杂记
    python3新式类的解析顺序-C3算法由来-mro-resolution-ordercanvas文本绘制-参考链接......
  • 【通知】▁▂▃ Himi 最新著作《iOS游戏编程之从零开始—Cocos2d-x与cocos2d引擎游戏
       2013年新年,Himi的第二本著作:《iOS游戏编程之从零开始—Cocos2d-x与cocos2d引擎游戏开发》一书正式发售;(大家可以到新华书店、淘宝、拍拍、当当、亚马逊等进行购买); ......
  • GL-Being assertive 20221123
    Time2022.11.23Wednesday23:30-00:15TopicBeingassertivePracticegivingyouropinionsinano-nonsenseway,andseewhoisthemost'assertive'personin......
  • React---编程式导航/声明式导航
    声明式导航通过NavLink来实现路由跳转的导航编程式导航通过js来实现路由的跳转import{Component}from"react";​exportdefaultclassNewsextendsCo......
  • 学fpga(在线verilog编程)
        很多编程不方便的同学,可以利用在线工具进行编程。比如现在,https://hdlbits.01xz.net/wiki/Main_Page这个网站就非常不错,适合暂时还没有安装quartus、vivado的同......
  • 尝试了一下编程挑战赛
    不太习惯比赛用的编辑器,没法做过程的调试,这个就很考基本功了。一是是对算法已经完全心理有数,要不然调试起来很困难。二是要对语法非常熟,像我这种经常要查语法帮助的人,哈哈......
  • python 编程技巧
     元组命名如何为元组中的每个元素命名,提高程序可读性案例:学生信息系统中数据为固定格式:(名字,年龄,性别,邮箱地址,......)学生数量很大为了减小存储开销,对每个学生信息用元......
  • 真的,Java并发编程入门看这个就够了
    Java并发编程学习之02Java并发编程入门指南(真的,Java并发编程入门看这个就够了)1.Java天生多线程importjava.lang.management.ManagementFactory;importjava.lang......
  • 真的,Java并发编程基础入门看这个就够了
    Java并发编程学习之02Java并发编程入门指南@目录1.Java天生多线程2.Java启动多线程实现方式2.1实现代码2.2Thread和Runnable的区别2.3start和run方法的区别3.Java......