在VC6.0的平台上使用assert_param()

发布于 2014-08-30  1.44k 次阅读


原型定义:

void assert( int expression );

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

下面来看看一段代码:

 

运行结果为:

看看运行结果,因为我们给定的i初始值为1,所以使用assert(i++);语句的时候不会出现错误,进而执行了i++,所以其后的打印语句输出值为2。如果我们把i的初始值改为0,那么就回出现如下错误。
Assertion failed: i++, file X:xxxxxxxxx.cpp, line 8
Press any key to continue

虽然断言便于定位出错点,但使用时也需要注意,应该按规则来,不要随意使用,避免弄巧成拙。比如在以上的例子中,在断言中,i的值也会发生改变,这在代码调试完成后,要进行发布,去除断言时,就会出错了。所以在断言语句内,不应该对变量作出任何的操作。

断言的使用注意事项归纳如下:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

使用assert_param的缺点在于,频繁的调用会极大的影响程序的性能,增加额外的开销。在VC6.0中,可以在包含#include的语句之前插入#define NDEBUG来禁用assert_param调用。可以察看assert.h头文件,深入了解assert_param的使用方法。

接下来通过一个例子,加深对assert_param的使用,并介绍如何编写更符合自己要求的assert断言:

运行结果为:

这个结果,相信大家都很容易就能理解。

接下来介绍的是如何编写也属于自己的断言:

备注:在C99中才有func,在VC6.0中,已没有了,所以现在如果要使用的话,要删除。

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

运行结果如下:

[EXAM]Error Report file_name: assert3.c, function_name: main, line 31
Aborted

此时就不会再执行接下来的语句。从上面的例子中我们可以知道自己编写的断言可以比直接调用assert_param宏得到更多的信息,主要是由于我们自己编写的断言更加的具有灵活性,可以根据自己的需要来打印出不同的信息,同时也可以对于不同类型的错误或者警告信息使用不同的断言,这也是在工程代码中经常使用的做法。

细心的读者会发现在宏定义中,使用了do{}while(0),使用它的好处是什么?或许在上面的代码中没有体现出来,那么我们看看下面的代码就会知道了。

运行结果:

print_2
Press any key to continue

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

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

error C2181: illegal else without matching if

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

版权声明:文章的内容多参考“C语言的那些小秘密之断言”,非个人原创。


公交车司机终于在众人的指责中将座位让给了老太太