C语言调试技巧——在设计的同时考虑调试
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语言调试技巧——在设计的同时考虑调试
在使用C语言编写程序时,谁都不能保证一次编写正确。如果能有一种方法能够在我们出错的时候编译出现测试调试信息,在程序验证正确的情况下取消测试调试信息的编译,那该是多么令人激动啊!而这种方法在C语言中确实存在!如果能够在整个程序的生命周期中都能快速定位错误点,那将会极大的提高编程效率;如果将这种方法应用到整个工程中,那整个项目测试的周期都会得到很大程度上的缩短。那么这是怎样的一种方法呢?这就是断言,实质上是一种带参宏。
一、标准C语言中使用断言。
1、标准库中的断言:
void assert( int expression );
assert宏的原型定义在
下面来看看一段代码:
#include
#include
int main( void )
{
int i;
i=1;
assert(i++);
printf("%d\n",i);
return0;
}
运行结果为:
看看运行结果,因为我们给定的i初始值为1,所以使用assert(i++);语句的时候不会出现错误,进而执行了i++,所以其后的打印语句输出值为2。如果我们把i的初始值改为0,那么就回出现如下错误。
Assertion failed: i++, file E:\fdsa\assert2.cpp, line 8
Press any key to continue
是不是发现根据提示很快就能定位出错点呢?!
2、断言使用规则。
断言语句不是永远会执行,可以屏蔽也可以启用,这就要求assert不管是在屏蔽还是启用的情况下都不能对我们本身代码的功能有所影响,这样的话刚才我们在代码中使用了一句assert(i++);是不妥的,因为我们一旦禁用了assert,i++的语句就得不到执行,对于接下来i值的使用就会出现问题了,所以对于这样的语句我们应该是要分开来实现,写出如下两句来替代, assert(i); i++;,所以这就对于断言的使用有了相应的要求,那么我们一般在什么情况下使用断言呢?主要体现在一下几个方面:
a.可以在预计正常情况下程序不会到达的地方放置断言。(如assert (0);)
b.使用断言测试方法执行的前置条件和后置条件。
c.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如某个变量的变化范围)
对于上面的前置条件和后置条件可能有的读者还不是很了解,那么看看下面的解释你就明白了。
前置条件断言:代码执行之前必须具备的特性
后置条件断言:代码执行之后必须具备的特性
前后不变断言:代码执行前后不能变化的特性
当然在使用的断言的过程中会有一些我们应该注意的事项和养成一些良好的习惯,如:a.每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,我们就无法直观的判断是哪个条件失败
b.不能使用改变环境的语句,就像我们上面的代码改变了i变量,在实际编写代码的过程中是不能这样做的
c.assert和后面的语句应空一行,以形成逻辑和视觉上的一致感,也算是一种良好的编程习惯吧,让编写的代码有一种视觉上的美感
d.有的地方,assert不能代替条件过滤
e.放在函数参数的入口处检查传入参数的合法性
f.断言语句不可以有任何边界效应
上面那么多的文字,似乎很枯燥,但是没办法,我们不能急功近利,还是要先坚持看完文字描述部分,这样在下面我们分析代码的过程中就能很快知道为什么会出现那样的问题了,也能在自己编写代码的时候熟练的使用assert,给自己的代码调试带来极大的便利,尤其是你在用C语言做工程项目的时候,如果你能够在你的代码中合理的使用assert,能使你创建更稳定、质量更好且不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。单元测试必须使用断言,除了类型检查和单元测试外,断言还提供了一种确定各种特性是否在程序中得到维护的极好的方法。但凡优秀的程序员都能够在自己代码中很好的使用assert,编写出高质量的代码来。
3、assert的缺点。
使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。所以在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用。
4、断言的一个示例分析。
先看代码:
#include
//#define NDEBUG
#include
int copy_string(char from[],char to[])
{
int i=0;
while(to[i++]=from[i]);
printf("%s\n",to);
return1;
}
int main()
{
char str[]="this is a string!";
char dec_str[206];
printf("%s\n",str);
assert(copy_string(str,dec_str));
printf("%s\n",dec_str);
return0;
}
运行结果为:
在以上代码的开头部分我们把#define NDEBUG给注释掉了,所以我们启用了assert,main 函数中使用了assert(copy_string(str,dec_str));来实现copy_string函数的调用,在
copy_string函数中我们使用了一句return 1,所以最终的函数调用结果就等价于是
assert(1),所以接下来继续执行assert下面的打印语句,最终成功的打印了三条输出语句,如果我们把开头的注释部分打开,结果就只能成功的输出起始部分一条打印语句。
二、实现自己的断言
接下来我们看看另外一段代码:
#include
//#undef _EXAM_ASSERT_TEST_ //禁用
#define _EXAM_ASSERT_TEST_ //启用
#ifdef _EXAM_ASSERT_TEST_ //启用断言测试
void assert_report( const char * file_name, const char * function_name,
unsigned int line_no )
{
printf( "\n[EXAM]Error Report file_name: %s, function_name: %s,
line %u\n",
file_name, function_name, line_no );
}
#define ASSERT_REPORT( condition ) \
do{ \
if ( condition ) \
NULL; \
else \
assert_report( __FILE__, __func__, __LINE__ ); \
}while(0)
#else// 禁用断言测试
#define ASSERT_REPORT( condition ) NULL
#endif/* end of ASSERT */
int main( void )
{