C语言调试技巧 - 在设计的同时考虑调试
更新时间:2023-12-27 08:36:01 阅读量: 教育文库 文档下载
- c语言怎么调试推荐度:
- 相关推荐
C语言调试技巧——在设计的同时考虑调试
在使用C语言编写程序时,谁都不能保证一次编写正确。如果能有一种方法能够在我们出错的时候编译出现测试调试信息,在程序验证正确的情况下取消测试调试信息的编译,那该是多么令人激动啊!而这种方法在C语言中确实存在!如果能够在整个程序的生命周期中都能快速定位错误点,那将会极大的提高编程效率;如果将这种方法应用到整个工程中,那整个项目测试的周期都会得到很大程度上的缩短。那么这是怎样的一种方法呢?这就是断言,实质上是一种带参宏。
一、 标准C语言中使用断言。 1、标准库中的断言:
void assert( int expression );
assert宏的原型定义在中,其作用是先计算表达式 expression ,如果expression的值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用abort 来终止程序运行。
下面来看看一段代码: #include
int i; i=1;
assert(i++);
printf(\,i); return 0; }
运行结果为:
看看运行结果,因为我们给定的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
int copy_string(char from[],char to[]) {
int i=0;
while(to[i++]=from[i]); printf(\,to); return 1; }
int main() {
char str[]=\; char dec_str[206]; printf(\,str);
assert(copy_string(str,dec_str)); printf(\,dec_str); return 0; }
运行结果为:
在以上代码的开头部分我们把#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( \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 ) {
int i; i=0;
// assert(i++); ASSERT_REPORT(i); printf(\,i); return 0; }
运行结果如下:
[EXAM]Error Report file_name: assert3.c, function_name: main, line 29 0
细心的读者会发现我们并没有使用断言来结束当前程序的执行,所以在断言下面的printf成功的打印出了i的当前值,当然我们也可以做适当的修改,在断言出发现错误,那么就调用 abort();来使当前正在执行的程序异常终止,修改如下: #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( \, file_name, function_name, line_no ); abort(); }
#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 ) {
int i; i=0;
// assert(i++); ASSERT_REPORT(i); printf(\,i); return 0; }
运行结果如下:
[EXAM]Error Report file_name: assert3.c, function_name: main, line 31 Aborted
此时就不会在执行接下来的打印语句了。看看我们自己的实现方式就知道,我们自己编写的断言可以比直接调用assert宏可以得到更多的信息量,主要是由于我们自己编写的断言更加的具有灵活性,可以根据自己的需要来打印输出不同的信息,同时也可以对于不同类型的错误或者警告信息使用不同的断言,这也是在工程代码中经常使用的做法。 三、总结
上面内容已给出了在C语言编写代码的过程中断言较为详细的使用,其中后面使用我们自己实现的断言算得上是一个比较经典的断言设计方法了,读者可以在自己以后编写C语言代码的过程中参考下。
备注:
为什么在定义语句块宏时使用do{}while(0),或许在以上的代码中并没有体现出来,那么我们看看下面的代码你就知道了。 #include
printf(\); }
void print_2(void) {
printf(\); }
#define printf_value() \\ print_1(); \\ print_2(); \\ int main( void ) {
int i=0; if(i==1)
printf_value(); return 0; }
运行结果:
print_2
Press any key to continue
看了上面运行结果可能有的读者会很疑惑为什么会出现以上的错误呢?!if语句的条件不满
足,那么print_value()函数应该不会被调用啊,怎么会打印呢。如果我们把上面的
printf_value()替换为 print_1(); print_2();,就会很清楚的发现if语句在此的作用仅仅是不调用print_1();,而print_2();在控制之外,所以出现了上面的结果,有的读者可能会马上想到我们加上一个{}不就好了吗,在这里的确是加一个{}就可以了,因为这里是一个特殊情况,没有else语句,如果我们在以上的宏定义中使用{},加入else语句后再来看看代码。 #include
printf(\); }
void print_2(void) {
printf(\); }
#define printf_value() \\ { \\
print_1(); \\ print_2();} int main( void ) {
int i=0; if(i==1)
printf_value(); else
printf(\); return 0; }
看似正确的代码,我们编译就会出现如下错误: error C2181: illegal else without matching if
为什么会出现这样的错误呢?因为我们编写C语言代码时,在每个语句后面加分号是一种约定俗成的习惯,以上代码中我们在printf_value()语句后面加了一个分号,正是由于这个分号的作用使得else没有与之相对应的if,所以编译出错。但是如果我们使用do{}while(0)就不会出现这些问题,所以我们在编写代码的时候应该学会在宏定义中使用do{}while(0)。
足,那么print_value()函数应该不会被调用啊,怎么会打印呢。如果我们把上面的
printf_value()替换为 print_1(); print_2();,就会很清楚的发现if语句在此的作用仅仅是不调用print_1();,而print_2();在控制之外,所以出现了上面的结果,有的读者可能会马上想到我们加上一个{}不就好了吗,在这里的确是加一个{}就可以了,因为这里是一个特殊情况,没有else语句,如果我们在以上的宏定义中使用{},加入else语句后再来看看代码。 #include
printf(\); }
void print_2(void) {
printf(\); }
#define printf_value() \\ { \\
print_1(); \\ print_2();} int main( void ) {
int i=0; if(i==1)
printf_value(); else
printf(\); return 0; }
看似正确的代码,我们编译就会出现如下错误: error C2181: illegal else without matching if
为什么会出现这样的错误呢?因为我们编写C语言代码时,在每个语句后面加分号是一种约定俗成的习惯,以上代码中我们在printf_value()语句后面加了一个分号,正是由于这个分号的作用使得else没有与之相对应的if,所以编译出错。但是如果我们使用do{}while(0)就不会出现这些问题,所以我们在编写代码的时候应该学会在宏定义中使用do{}while(0)。
正在阅读:
C语言调试技巧 - 在设计的同时考虑调试12-27
温暖在我的身边作文500字07-08
礼拜程序及念词03-11
大数的认识典型练习题(用)(1).06-29
教你二十种饼的做法《不需发面》 - 图文06-08
江西省鹰潭市2018-2019学年高二上学期期末质量检测数学(文)试题(含解析)05-08
县环卫工人权益保障情况报告04-04
实用经贸法语 实用笔记08-28
- exercise2
- 铅锌矿详查地质设计 - 图文
- 厨余垃圾、餐厨垃圾堆肥系统设计方案
- 陈明珠开题报告
- 化工原理精选例题
- 政府形象宣传册营销案例
- 小学一至三年级语文阅读专项练习题
- 2014.民诉 期末考试 复习题
- 巅峰智业 - 做好顶层设计对建设城市的重要意义
- (三起)冀教版三年级英语上册Unit4 Lesson24练习题及答案
- 2017年实心轮胎现状及发展趋势分析(目录)
- 基于GIS的农用地定级技术研究定稿
- 2017-2022年中国医疗保健市场调查与市场前景预测报告(目录) - 图文
- 作业
- OFDM技术仿真(MATLAB代码) - 图文
- Android工程师笔试题及答案
- 生命密码联合密码
- 空间地上权若干法律问题探究
- 江苏学业水平测试《机械基础》模拟试题
- 选课走班实施方案
- 调试
- 考虑
- 同时
- 语言
- 技巧
- 设计