文档开发者中心
我的

【综合】【码手防御-4】断言的使用

作者:HW-HM


上一篇文章【码手防御-3】防御式编程技术,我们讨论了防御式编程相关的技术,本篇将介绍断言的使用。

先回顾一下契约式设计的概念:明确定义软件模块的权力和义务,并通过程序来检查,以确保程序正确性。这里的检查就是对先验条件、后验条件和不变条件的检查,通过对这些条件进行检查,来确保程序正确。在确保程序正确之后,这些检查就可以去掉了,这时的程序就是我们的发布版本。而在开发期间,我们要尽可能多发现程序错误,这些断言都是有效的,这时的程序版本就是我们的调测版本。这里的检查通常是通过断言来做的,断言失败就表明程序有Bug。

如下的代码可以演示断言的效果。

#include 'stdafx.h'

#include <assert.h>

#include <stdio.h>

int main()

{

assert(0 == 1);//断言失败

return 0;

}

利用VC编译Debug版本,并运行,效果如下图。打印出断言字符串、文件名、行号并弹出了提示框。如果是在Linux做类似操作会发现还会“Abort (core dumped)”,也就是说产生了core文件。

如果是release版本,就跟没有assert一样。这没什么奇怪的,看下assert.h中assert定义就知道了,你看到的定义与下面的类似:

#ifdef NDEBUG

#define assert(expr) ((void)0)

#else

#define assert(expr) (void((expr)? 0: (__assert_fail (__STRING(expr), __FILE__, __LINE__), 0)))

#endif

可见assert是一个宏,定义了NDEBUG(发布版本),做了空操作,编译器不会为之生成代码。未定义NDEBUG时(调测版本),断言成功也是啥都不做,失败时则将断言字符串、文件名、行号传给了一个函数,这个函数通常会打印出相关内容,并终止程序运行。

码手留意1:断言只在调测版本中有效,在发布版本中是无效的,不要把需要执行的代码放入断言中。

码手留意2:断言是为了确保程序正确,断言失败意味着程序Bug。对来源于内部系统的可靠的数据使用断言,而不要对外部不可靠的数据使用断言,对于外部不可靠数据,应该使用错误处理代码。例如用户应该输入数字,结果用户输入中包含了字母,再如内存分配失败,这些应该使用错误处理。

那我们可以用断言来做什么呢?

断言是为了确保程序正确,那么断言本身不能对程序正确性产生干扰,如果断言中调用了函数,那么这个函数必须是正确的,并且这个函数不能有副作用(只能读)。契约式设计原则的前4条,就是服务于这个目的,当然则4条同时也方便了契约的检查。也就是说契约式检查中只能调用查询,不能调用命令,而且这些查询必须是正确的。

码手留意3:断言中只能调用无副作用(只读)并且正确的函数。

可是有的验证并不是一个断言就可以搞定。譬如我写了一个快速排序,想验证排序结果是否正确,一种方法是写一个简单的冒泡排序(这个正确性比较容易保证),然后用这个冒泡排序的结果与快速排序结果进行比较。示意代码如下:

void quicksort(int a[], int num)

{

#ifdef DEBUG

int b = new int[num];

memcpy(…);//将a复制到b

#endif

……

#ifdef DEBUG

bubblesort(b, num);

for(int i = 0; i < num; i++)

assert(a[i] = b[i]);

#endif

}

我们在原来函数开头将a复制到了数组b,并在函数唯一出口前增加了与冒泡排序的结果对比检查,这些代码都是调测代码(不是测试代码),在正式的版本中不能出现,所以加了#ifdef DEBUG。你也许觉得这些代码并不影响程序正确性啊,可是它们会影响性能,影响存储空间,影响……。

码手留意4:任何调测代码在发布版本中都不应该出现。C或C++代码可以用#ifdef DEBUG条件编译选项控制。

那么我们可以用断言来做什么呢?

码手留意5:可以使用断言来做如下事情

1、 检查所有的假定(包括契约),例如函数入参合法性、函数返回前后验条件检查。一些组织会要求函数只有一个出口,一个原因就是为了方便后验条件的检查。

2、 检查不可能发生的情况,这些情况的发生意谓着程序Bug。例如在循环结束时,检查结束条件是否正确。

3、 检查出无定义特性的非法使用。譬如ANSI C中memcpy如果在存储空间相互重叠的对象之间进行了拷贝,其结果无定义。如果你有类似这样的函数,可以使用断言进行检查,这样就可以避免函数调用者蒙对了,结果那天你一改实现,它就嗝屁了。当然最好是删除无定义特性。


点赞
收藏
回复
11
分享
举报
浏览1995 发布于2019-11-25 06:48未知归属地
全部评论
最多点赞
最新发布
最早发布

对断言的使用总结很到位

11楼回复于2019-11-25 08:54 未知归属地

都是干货,不错

10楼回复于2019-11-25 08:46 未知归属地

好文一定要分享给其他小伙伴

9楼回复于2019-11-25 08:45 未知归属地

怒赞一个

8楼回复于2019-11-25 08:01 未知归属地

码手系列文章都很不错,值得读

7楼回复于2019-11-25 08:01 未知归属地

希望论坛能多多连载这类有干货和技术含量的文章

6楼回复于2019-11-25 07:42 未知归属地

系列文章都拜读过了,不错哦

5楼回复于2019-11-25 07:42 未知归属地

系列阅读:【码手防御-5】契约式设计详例

4楼回复于2019-11-25 07:15 未知归属地

系列阅读:【码手防御-3】防御式编程技术

3楼回复于2019-11-25 07:15 未知归属地

系列阅读:【码手防御-1】防御式编程

2楼回复于2019-11-25 07:14 未知归属地
共2页
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。