在调试程序时,经常会用到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版本。