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

1、原型函数

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

1 void assert(int expression);

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

2、详细释义

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

1 #ifdef NDEBUG
2 #define assert(e) ((void)0)
3 #else
4 #define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
5 #endif

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

1 #define NDEBUG  //定义NDEBUG
2 #ifdef NDEBUG
3 #define assert(e) ((void)0)
4 #else
5 #define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
6 #endif
  • 定义NDEBUG时:

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

  • 未定义NDEBUG时:

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

3、用法举例

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

 1 #include <stdio.h>
2 #include <assert.h>
3 void main()
4 {
5 int i = 8;
6 assert(i > 0);
7 printf("i = %d\n", i);
8 i = -8;
9 assert(i > 0);
10 printf("i = %d\n", i);
11 }

  可以看出在程序中使用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版本中检查排除掉这种错误,以免影响后续程序的执行。

1 #include <stdio.h>
2 #include <assert.h>
3 void fun(int a, int b)
4 {
5 assert(b != 0);
6 int i = a / b;
7 }

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

 1 #include <stdio.h>
2 #include <assert.h>
3 void fun(int a, int b)
4 {
5 if(a > 0)
6 ...
7 else if(a < 0)
8 ...
9 else
10 ...
11 }

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

1 #include <stdio.h>
2 #include <assert.h>
3 void fun(int a, int b)
4 {
5 if(0 == b)
6 return;
7 int i = a / b;
8 }
  • 用于判断函数的入参

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

 1 #include <stdio.h>
2 #include <assert.h>
3 void fun1(int a)
4 {
5 assert(a > 0);
6 ...
7 }
8 void fun2(int *p)
9 {
10 assert(p != NULL);
11 ...
12 }
  • 不要使用影响正常逻辑的判断条件语句

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

 1 #include <stdio.h>
2 #include <assert.h>
3 void main()
4 {
5 int i = 0;
6 while(i <= 110)
7 {
8 assert(i++ < 100);
9 printf("i = %d\n",i);
10 }
11 }
  • 不要用多个判断条件语句

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

 1 #include <stdio.h>
2 #include <assert.h>
3 void fun1(int a, int b) //错误使用
4 {
5 assert(a > 0 && b > 5);
6 ...
7 }
8 void fun2(int a, int b) //正确使用
9 {
10 assert(a > 0);
11 assert(b > 5);
12 ...
13 }

更多技术内容和书籍资料获取敬请关注微信公众号“明解嵌入式”

防御式编程之断言assert的使用的更多相关文章

  1. cocos2d-x设计模式发掘之五:防御式编程模式

    http://www.ityran.com/archives/2105 本文由子龙山人原创,泰然授权转载,转载请注明出处并通知子龙山人! 声明:防御式编程是提高程序代码质量的一种手段,它不能算是真正意 ...

  2. 《Code Complete》ch.8 防御式编程

    WHAT? 主要思想:子程序不应因传入参数错误而被破坏 WHY? 保护程序免遭非法输入的破坏 HOW? 断言 assert denominator != 0 : "denominator s ...

  3. 软件工程 - 防御式编程EAFP vs LBYL

    概念 EAFP:easier to ask forgiveness than permission LBYL:look before you leap 代码 # LBYL def getUserInf ...

  4. python2学习------基础语法3(类、类的继承、类成员函数、防御式编程)

    1.类的定义以及实例化 # 类定义 class p: """ this is a basic class """ basicInfo={&q ...

  5. 【SQLSERVER学习笔记】进攻式编程

    一般的编程语言建议是进行防御式编程,在开始处理之前先检查所有参数的合法性.但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势.*/ --我们SP中常见的防御式编程示例:--场景一: ...

  6. 断言(assert)的用法

    我一直以为assert仅仅是个报错函数,事实上,它居然是个宏,并且作用并非“报错”. 在经过对其进行一定了解之后,对其作用及用法有了一定的了解,assert()的用法像是一种“契约式编程”,在我的理解 ...

  7. C语言中断言ASSERT

    我一直以为assert仅仅是个报错函数,事实上,它居然是个宏,并且作用并非"报错". 在经过对其进行一定了解之后,对其作用及用法有了一定的了解,assert()的用法像是一种&qu ...

  8. 断言Assert的使用

     转载地址:http://www.cnblogs.com/moondark/archive/2012/03/12/2392315.html 我一直以为assert仅仅是个报错函数,事实上,它居然是个宏 ...

  9. Python中何时使用断言 assert

    使用断言的最佳时机偶尔会被提起,通常是因为有人误用,因此我觉得有必要写一篇文章来阐述一下什么时候应该用断言,为什么应该用,什么时候不该用. 对那些没有意识到用断言的最佳时机的人来说,Python的断言 ...

  10. .NET 4.0 中的契约式编程

    契约式编程不是一门崭新的编程方法论.C/C++ 时代早已有之.Microsoft 在 .NET 4.0 中正式引入契约式编程库.博主以为契约式编程是一种相当不错的编程思想,每一个开发人员都应该掌握.它 ...

随机推荐

  1. vscode常用快捷键及插件

    macOS 全局 Command + Shift + P / F1 显示命令面板 Command + P 快速打开 Command + Shift + N 打开新窗口 Command + W 关闭窗口 ...

  2. 关于使用kubeoperator搭建k8s集群使用containerd作为容器运行时,从自己搭建的habor仓库拉取镜像的有关说明

    1.kubepi界面添加habor仓库信息,并授权给k8s集群 这一步的操作是当在工作负载选择从harbor仓库拉取镜像时会自动创建有关的secrets信息,从而不用事先手动创建了(有别于kuboar ...

  3. kubeadm 使用 Calico CNI 以及外部 etcd 部署 kubernetes v1.23.1 高可用集群

    文章转载自:https://mp.weixin.qq.com/s/2sWHt6SeCf7GGam0LJEkkA 一.环境准备 使用服务器 Centos 8.4 镜像,默认操作系统版本 4.18.0-3 ...

  4. EFK-3: ES多实例部署

    转载自:https://mp.weixin.qq.com/s?__biz=MzUyNzk0NTI4MQ==&mid=2247483816&idx=1&sn=bfaf70613b ...

  5. Vmware虚拟机设置主机端口映射

    转载自:https://blog.csdn.net/Mrqiang9001/article/details/80820321

  6. css语言

    css:样式表.级联样式表.层叠样式表 css写在style标签里面,放在head标签中:大括号中写键值对语法 color:文字颜色 Font-family:字体 Font-size:字号 text- ...

  7. .NET平台下一个你不知道的框架,我只想说两个字:“牛逼”

    框架内容 零度框架是一套基于微服务和领域模型驱动设计的企业级快速开发框架,基于微软 .NET 6 + React 最新技术栈构建,容器化微服务最佳实践,零度框架的搭建以开发简单,多屏体验,前后端分离, ...

  8. PHP cURL抓取网上图片

    cURL的底层是由一个命令行工具实现的,用于获取远程文件或传输文件,更多的情况是用来模拟get/post表单提交.也可以用户文件上传,爬取文件,支持FTP/FTPS,HTTP/HTTPS等协议,通俗来 ...

  9. 某OA系统需要提供一个假条审批的模块,如果员工请假天数小于3天,主任可以审批该请假条;如果员工请假天数大于等于3天,小于10天,经理可以审批;如果员工请假天数大于等于10天,小于30天,总经理可以审批

    某OA系统需要提供一个假条审批的模块,如果员工请假天数小于3天,主任可以审批该请假条:如果员工请假天数大于等于3天,小于10天,经理可以审批:如果员工请假天数大于等于10天,小于30天,总经理可以审批 ...

  10. java 入土--集合详解

    java 集合 集合是对象的容器,实现了对对象的常用的操作,类似数组功能. 和数组的区别: 数组长度固定,集合长度不固定 数组可以存储基本类型和引用类型,集合只能存储引用类型 使用时需要导入类 Col ...