C语言assert

在使用C语言编写工程代码时,我们总会对某种假设条件进行检查,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题可以用断言来进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。

通过上面的讲解我们对于断言算是有了一个大概的了解,那么接下来我们就来看看C语言中assert宏在代码中的使用。

原型定义:

void assert( intexpression );

assert宏的原型定义在<assert.h>中,其作用是先计算表达式 expression ,如果expression的值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用abort 来终止程序运行。

下面来看看一段代码:


640?wx_fmt=png

看看运行结果,因为我们给定的i初始值为1,所以使用assert(i++);语句的时候不会出现错误,进而执行了i++,所以其后的打印语句输出值为2。如果我们把i的初始值改为0,那么就回出现如下错误。

640?wx_fmt=png 

是不是发现根据提示很快就能定位出错点呢?!既然assert这么便于定位出错点,看来的确我们有必要熟练的在代码中使用它,但是什么东西的使用都是有规则的,assert的使用也不例外。

言语句不是永远会执行,可以屏蔽也可以启用,这就要求assert不管是在屏蔽还是启用的情况下都不能对我们本身代码的功能有所影响,这样的话刚才我们在 代码中使用了一句assert(i++);是不妥的,因为我们一旦禁用了asserti++的语句就得不到执行,对于接下来i值的使用就会出现问题了, 所以对于这样的语句我们应该是要分开来实现,写出如下两句来替代, assert(i);i++;,所以这就对于断言的使用有了相应的要求,那么我们一般在什么情况下使用断言呢?主要体现在一下几个方面:

1.可以在预计正常情况下程序不会到达的地方放置断言。(如assert(0);

2.使用断言测试方法执行的前置条件和后置条件

3.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如某个变量的变化范围)

对于上面的前置条件和后置条件可能有的读者还不是很了解,那么看看下面的解释你就明白了。

前置条件断言:代码执行之前必须具备的特性

后置条件断言:代码执行之后必须具备的特性

前后不变断言:代码执行前后不能变化的特性

当然在使用的断言的过程中会有一些我们应该注意的事项和养成一些良好的习惯,如:

1.每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,我们就无法直观的判断是哪个条件失败

2.不能使用改变环境的语句,就像我们上面的代码改变了i变量,在实际编写代码的过程中是不能这样做的

3.assert和后面的语句应空一行,以形成逻辑和视觉上的一致感,也算是一种良好的编程习惯吧,让编写的代码有一种视觉上的美感

4.有的地方,assert不能代替条件过滤

5.放在函数参数的入口处检查传入参数的合法性

6.断言语句不可以有任何边界效应

面那么多的文字,似乎很枯燥,但是没办法,我们不能急功近利,还是要先坚持看完文字描述部分,这样在下面我们分析代码的过程中就能很快知道为什么会出现那样的问题了,也能在自己编写代码的时候熟练的使用assert,给自己的代码调试带来极大的便利,尤其是你在用C语言做工程项目的时候,如果你能够在你的 代码中合理的使用assert,能使你创建更稳定、质量更好且不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。单元测试 必须使用断言,除了类型检查和单元测试外,断言还提供了一种确定各种特性是否在程序中得到维护的极好的方法。但凡优秀的程序员都能够在自己代码中很好的使assert,编写出高质量的代码来。

说了assert这么多的有点,当然也要说说它的缺点了。

使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。所以在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用。

以上我们都是在围绕着assert宏在讲解,仅仅是教会大家如何来使用assert宏,那么接下来看看我们如何来实现自己的断言呢?

接下来我们看看另外一段代码:

640?wx_fmt=png

运行结果如下:

640?wx_fmt=png

细心的读者会发现我们并没有使用断言来结束当前程序的执行,所以在断言下面的printf成功的打印出了i的当前值,当然我们也可以做适当的修改,在断言出发现错误,那么就调用 abort();来使当前正在执行的程序异常终止,修改如下:

640?wx_fmt=png

运行结果如下:

640?wx_fmt=png 

此时就不会在执行接下来的打印语句了。看看我们自己的实现方式就知道,我们自己编写的断言可以比直接调用assert宏可以得到更多的信息量,主要是由于 我们自己编写的断言更加的具有灵活性,可以根据自己的需要来打印输出不同的信息,同时也可以对于不同类型的错误或者警告信息使用不同的断言,这也是在工程代码中经常使用的做法。如果你在关注代码运行结果的同时也认真的阅读了我的代码,你会发现其中我在宏定义中使用了一个do{}while(0),使用它有什么好处呢,或许在以上的代码中并没有体现出来,那么我们看看下面的代码你就知道了。

640?wx_fmt=png

 运行结果:

640?wx_fmt=png

看了上面运行结果可能有的读者会很疑惑为什么会出现以上的错误呢?!if语句的条件不满足,那么print_value()函数应该不会被调用啊, 怎么会打印呢。如果我们把上面的printf_value()替换为 print_1();  print_2();,就会很清楚的发现if语句在此的作用仅仅是不调用print_1();,而print_2();在控制之外,所以出现了上面的结 果,有的读者可能会马上想到我们加上一个{}不就好了吗,在这里的确是加一个{}就可以了,因为这里是一个特殊情况,没有else语句,如果我们在以上的 宏定义中使用{},加入else语句后再来看看代码。

640?wx_fmt=png

看似正确的代码,我们编译就会出现如下错误:

640?wx_fmt=png

为什么会出现这样的错误呢?因为我们编写C语言代码时,在每个语句后面加分号是一种约定俗成的习惯,以上代码中我们在printf_value() 语句后面加了一个分号,正是由于这个分号的作用使得else没有与之相对应的if,所以编译出错。但是如果我们使用do{}while(0)就不会出现这 些问题,所以我们在编写代码的时候应该学会在宏定义中使用do{}while(0)

 

640?wx_fmt=jpeg


  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值