- 9.2 如何设计一种灵活的断言
- include<stdio.h>
- include<stdlib.h>
- ifndef NDEBUG
- define assert(p)if(!(p))\
- else
- define assert(p)(void)(0)
- endif
- define assert(p)if(!(p))\
- include<stdio.h>
- include<stdlib.h>
- ifndef NDEBUG//启用断言测试
- define assert(condition)\
- else
- define assert(condition)NULL
- endif
- include<stdio.h>
- define printf_value()\
- include<stdio.h>
- define printf_value()\
9.2 如何设计一种灵活的断言
在实际应用时,可能需要根据具体情况灵活地设计断言,所以我们对断言的认识就不能仅停留在使用上面,应该学会设计自己的断言代码,以便在代码中灵活地使用断言。接下来就介绍如何自己在代码中编写断言,先来看下面断言的实现代码。
include<stdio.h>
include<stdlib.h>
//#define NDEBUG
ifndef NDEBUG
define assert(p)if(!(p))\
{\
fprintf(stderr,"Assertion failed:%s,file%s,
line%d\n",#p,FILE,LINE);\
abort();\
}
else
define assert(p)(void)(0)
endif
int sum(int a[],int n)
{
assert(n>0);
int i;
int sum=0;
for(i=0;i<n;i++)
{
sum+=a[i];
}
return sum;
}
int main()
{
int i=0;
int total;
int arr[]={2,3,4,7};
total=sum(arr,4);
printf("数组中的元素之和为:%d\n",total);
total=sum(arr,0);
return 0;
}
运行结果:
数组中的元素之和为:16
Assertion failed:n>0,file E:\fdsa\fdsag.cpp,line 16
以上代码中断言的实现方法是采用条件编译指令,这是为了在程序中可以开启或者关闭断言。下面来看开启断言的实现方法。
define assert(p)if(!(p))\
{\
fprintf(stderr,"Assertion failed:%s,file%s,line%d\n",#p,__
FILE,LINE__);\
abort();\
}
以上代码采用fprintf函数实现打印输出,其中,输出设备是stderr,同时在参数中使用了宏FILE和LINE,用于输出当前出错的文件名及行号。参数中还有一个“#p”,关于形参的使用在第2章“预处理”中已经进行了相应的讲解,读者可以查阅相关内容。再来看关闭断言的实现部分,这里使用的方法之前也介绍过,如果删掉代码中的“||”,关闭断言,那么宏assert(p)可用(void)(0)来替代,这时宏在代码中就像被清除了一样,不会有任何的作用。再来看以上代码中对宏的使用,通过宏来检测函数参数传递是否满足要求。
看上面采用VC++6.0编译实现断言的方法,输出信息中只包含文件名和行号,如果在Linux环境下采用gcc编译运行,那么可以得到更加具体的信息。例如,可以通过宏func获取出错位置的函数名,实现方法如下:
include<stdio.h>
include<stdlib.h>
//#define NDEBUG//禁用
ifndef NDEBUG//启用断言测试
void assert_report(const charfile_name,const charfunction_name,unsigned int line_no)
{
printf("file_name:%s,function_name:%s,line%u\n",
file_name,function_name,line_no);
abort();
}
define assert(condition)\
do{\
if(condition)\
NULL;\
else\
{\
printf("Error Report:%s",#condition);\
assertreport(FILE,func,_LINE);\
}\
}while(0)
else
define assert(condition)NULL
endif
int main(void)
{
int i;
i=0;
assert(i++);
printf("%d\n",i);
return 0;
}
运行结果:
root@ubuntu:/home#gcc assert.c-o assert
root@ubuntu:/home#./assert
Error Report:i++file_name:assert.c,function_name:main,line 33
Aborted
分析上面的运行结果,得到的出错信息包括文件名、函数名、行号,有了这些信息可以对出错位置进行快速定位。在断言的实现中使用了abort()函数,如果条件为假,那么就打印出错信息,同时运行abort函数终止程序的运行。
仔细阅读代码,会发现在宏定义中使用了一个do{}while(0)。使用它有什么好处呢?或许从以上代码中看不出来,那么看下面的代码就知道了。
include<stdio.h>
void print_1(void)
{
printf("成功地调用print_1函数\n");
}
void print_2(void)
{
printf("成功地调用print_2函数\n");
}
define printf_value()\
print_1();\
print_2();
int main(void)
{
int i=0;
if(i==1)
printf_value();
return 0;
}
运行结果:
成功地调用print_2函数
看了上面的运行结果,可能有的读者会疑惑为什么会出现这样的错误。如果if语句的条件不满足,那么print_value()函数应该不会被调用,怎么会出现打印结果呢?如果把上面的“printf_value()”替换为“print_1();print_2();”,就会很清楚地发现if语句在此处的作用仅仅是限制不调用“print_1();”,而“print_2();”在控制之外,所以出现了上面的结果。有的读者可能会马上想到加上括号{}就好了,这里加上括号{}的确就可以了,因为这里是一种特殊情况,即没有else语句。如果在宏定义中使用{}并加入else语句,结果会如何呢?看下面一段代码。
include<stdio.h>
void print_1(void)
{
printf("成功地调用print_1函数\n");
return;
}
void print_2(void)
{
printf("成功地调用print_2函数\n");
return;
}
define printf_value()\
{\
print_1();\
print_2();}
int main(void)
{
int i=0;
if(i==1)
printf_value();
else
printf("add else word!");
return 0;
}
运行结果:
error C2181:illegal else without matching if
看似正确的代码,编译的时候就会出现错误。为什么会出现这样的错误呢?因为在编写C语言代码时,在每个语句后面加分号是一种约定俗成的习惯,以上代码中的printf_value()语句后面有一个分号,这个分号的作用使else没有了与之相对应的if,所以编译时会出错。如果使用do while(0)就不会出现这种问题,因此在编写代码时应该学会在宏定义中使用do{}while(0)。