Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1014322
  • 博文数量: 327
  • 博客积分: 9995
  • 博客等级: 中将
  • 技术积分: 4319
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-25 11:21
文章存档

2011年(31)

2010年(139)

2009年(157)

我的朋友

分类: C/C++

2010-09-20 11:53:53

在调试程序时,经常会用到assert和printf之类的函数,我最近做的这个工程里就有几百个assert,在你自认为程序已经没有bug的时候,就要除去这些调试代码,应为系统在正常运行时这些用于调试的信息是无用的,而且会占用时间和空间。怎么删除呢,俺以前都是用笨方法,一个一个注释,能用注释也是经过改进的方法,俺最早都是删掉之后出了问题再重新写的,但是这次几百个一个一个删除的话可是要了俺的小命了,一首mp3听完,还不到一百个。以前看过st的函数库,老外的代码就是规范,俺现在的代码好多都是在st和ti那里照搬的,呵呵。
下面给出最简单的一种方法:
#define DEBUG
#ifdef DEBUG
#define PRINTF(x) printf x
#else
#define PRINTF(x) ((void)0)
#endif
使用时,PRINTF(( "Hello World!\n\r" ));
注意这里是两个括号,一个会报错的
不使用时,直接将"#define DEBUG"屏蔽掉
另外一个调试时常用的方法是assert,还是在一个头文件里,这里用的是STM32函数库的例子
#ifdef DEBUG 1
/*******************************************************************************
* Macro Name : assert_param
* Description : The assert_param macro is used for function's parameters check.
* It is used only if the library is compiled in DEBUG mode.
* Input : - expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* Return : None
*******************************************************************************/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((u8 *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(u8* file, u32 line);
#else
#define assert_param(expr) ((void)0)
#endif/* DEBUG */
//assert_failed此函数要自己定义
#ifdef DEBUG
/*******************************************************************************
* Function Name : assert_failed
* Description : Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* Input : - file: pointer to the source file name
* - line: assert_param error line source number
* Output : None
* Return : None
*******************************************************************************/
void assert_failed(u8* file, u32 line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1){
}
}
#endif
 

 
调试程序尤其是数据结构函数调用经常进行断言是一种好的习惯。本文简单介绍一下断言的使用方法,很简单Come on~
assert宏的原型定义在中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
请看下面的程序清单badptr.c:
#include
#include
#include
int main( void )
{
        FILE *fp;
   
        fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
        assert( fp );                            //所以这里不会出错
        fclose( fp );
   
        fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
        assert( fp );                            //所以这里出错
        fclose( fp );                            //程序永远都执行不到这里来
        return 0;
}
[root@localhost error_process]# gcc badptr.c
[root@localhost error_process]# ./a.out
a.out: badptr.c:14: main: Assertion `fp' failed.
已放弃
使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:
#include
#define NDEBUG
#include
用法总结与注意事项:
1)在函数开始处检验传入参数的合法性
如:
int resetBufferSize(int nNewSize)
{
//功能:改变缓冲区大小,
//参数:nNewSize 缓冲区新长度
//返回值:缓冲区当前长度
//说明:保持原信息内容不变      nNewSize<=0表示清除缓冲区
assert(nNewSize >= 0);
assert(nNewSize <= MAX_BUFFER_SIZE);
...
}
2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
不好: assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
好: assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);

3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
错误: assert(i++ < 100)
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert(i < 100)
          i++;           
     
4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感
5)有的地方,assert不能代替条件过滤
 

 
assert和if的区别:首先,这两个处于c语言中的不同等级,assert不过是系统提供的一个函数,而if则是关键字。其次,两个的语法也不一样,你可以写if(1){}但是写个assert(1){}却不对,当然,这个区别是第一点造成的在语义上,if就不用解释了吧。
 
assert的语义如下:在debug编译模式下,如果assert的表达式求值为false,就会中断程序;在release模式下,则没有任何操作。因此可以说,assert就是在调试模式下用来确保制定条件被满足的一种手法,比如说,你想确保a>0,你就可以写assert(a>0);如果在运行过程中,a<=0了,代码就会中断,利用调试器很容易发现问题所在。
 
在“应用程序调试"一书里,作者强烈推荐使用assert,他的代码让同事都抱怨assert太多了。。。。。。
 

 
assert()
 
C语言的assert(),中文翻译为断言,一旦触发,带调试信息的程序就会立刻退出并且给出错误信息,这一点是俺这样的IT民工都知道的,因此很多人对assert()是又爱又恨,爱她因为她能时不时给出点错误信息,恨她因为不去掉的话老是在你不希望她出现的时候蹦出来。
其实,我觉得对于程序而言,assert()的做法是一种勇敢的人生态度,面对不可逆转的错误,究竟是一错再错的执行下去,还是拿出一点舍身取义的勇气,终止自己继续执行的权力?assert()选择了后者。
但有些情况下不应该使用assert(),而应该利用其他方式来检测。一切都得视情况而定,例如某些函数的返回值检查:
ptr = malloc(1024);
assert(ptr);
采用assert()来做这样的判断是不明智的,但如果在一个接口定义良好的函数内使用assert()却可以保证程序能够正确的履行接口所定义的任务。例如:
void enter_without_lock(struct something_t * foo)
{
assert(NOT_HELD_MUTEX(foo->mutex));
........
}
因此如果你还在写程序,尤其是需要建立一个复杂的framework时,拿出点勇气来,多放几个assert()到这样的关键位置,不要轻易的去掉它,调试调试再调试,直到所有的assert()都不能正常通过,再发布你的release版本。
阅读(4531) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-09-20 13:52:40

ASSERTMSG宏的一种可能的实现是使它产生两个作用:一个是确认表达式,另一个是当断言否定时显示一个字符串。例如,若要打印memcpy的消息,应如以下形式调用ASSERTMSG: ASSERTMSG( pbTo >= pbFrom + size || pbFrom >= pbTo + size, “memcpy: the blocks overlap” ); 下面是ASSERTMSG宏的实现。你应将ASSERTMSG的定义放在头文件中,再将_AssertMsg例程放在一个方便的源文件内。 #ifdef DEBUG void_AssertMsg( char* strMessage ); /* 原型 */ #define ASSERTMSG( f, str ) \ if( f ) \ NULL \ else \

chinaunix网友2010-09-20 13:37:33

下面这个例子是显示assert函数控制程序异常。 源代码是: /************关于本文档******************************************** *filename: assert.c *purpose: 说明调用assert函数控制程序异常 *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com) Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 *date time:2008-01-26 17:33 上海大雪天,据说是多年不遇 *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 * 但请遵循GPL *Thanks to: * Ubuntu 本程序在Ubuntu 7.10系统上测试完全正常 * Google.com 我通常通过google搜索发现许多有用的资料 *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献! **