最近调试程序,发现ST的固件库中到处都是assert_param()函数,便查了一下,做个笔记。
assert_param语句是用于程序开发的时候,调试用的检测语句,帮助程序员始终选择有效的参数。默认是不开启的,可以无视它的存在。但是,当你在调试程序的时候,可以打开这个检测机制,调试完了再关闭。
所谓有效的参数是指满足规定范围的参数,比如某个参数的取值范围只能是小于3的正整数,如果给出的参数大于3,则这个assert_param()可以在编译时报告错误,使程序员可以及时发现错误,而不必等到运行程序时,因为程序运行错误而大费周折。
它确实在程序的运行上牺牲了效率(但只是在调试阶段),但在项目的开发上却帮助你提高了效率。
/* Exported types ------------------------------------------------------------*/
//导出类型
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the
//取消下一行的备注,扩充assert_param宏到标准外设库的驱动代码
Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT 1 */
/* Exported macro ------------------------------------------------------------*/
ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
//assert_param宏用于检查函数的参数
* @param 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.
//如果表达式错误(有语法问题),调用assert_failed 函数,它会打印出
//出错的源文件和发送错误的行号。如果表达式正确,不返回值。
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
else
#define assert_param(expr) ((void)0)
endif /* USE_FULL_ASSERT */
会发现它实际上是个宏,看它的条件编译语句,把USE_FULL_ASSERT定义后,即可打开assert_param这个参数检测机制。USE_FULL_ASSERT这个宏定义已经在文件中隐掉,把它的注释符号去掉即可。
define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
//表达式
assert_param(expr),断言函数,判定expr是不是0(或者空),如果为非0或空,执行语句(void)0,这是一个相当于空语句的表达式,不对程序产生任何影响。如果不0或空,那么调用assert_failed函数(终止程序,并打印文件名和行号)。
编译器内置宏: FILE, LINE,绍几个编译器内置的宏定义,这些宏定义不仅可以帮助我们完成跨平台的源码编写,灵活使用也可以巧妙地帮我们输出非常有用的调试信息。编译器在进行源码编译的时候,会自动将这些宏替换为相应内容。
ANSI C标准中有几个标准预定义宏(也是常用的):
LINE:在源代码中插入当前源代码行号;
FILE:在源文件中插入当前源文件名;
DATE:在源文件中插入当前的编译日期
TIME:在源文件中插入当前编译时间;
STDC:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。
三目运算符 ? 的用法: http://www.rationmcu.com/clang/382.html
ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t 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
assert_param(IS_SPI_DIRECTION_MODE(SPI_InitStruct->SPI_Direction));
分析: assert_param语句的作用是检测函数的参数是否符合该函数的要求,例如上面的函数中的参数就是SPI_InitStruct->SPI_Direction,我们找到IS_SPI_DIRECTION_MODE的定义,
define IS_SPI_DIRECTION_MODE(MODE) (((MODE) == SPI_Direction_2Lines_FullDuplex) || \
((MODE) == SPI_Direction_2Lines_RxOnly) || \
((MODE) == SPI_Direction_1Line_Rx) || \
((MODE) == SPI_Direction_1Line_Tx))
可以看到MODE的取值,也就是说,你要是把MODE取值4个定义内,assert_param()的结果就是(void)0,你要是把MODE取值4个定义外,就会调用assert_failed函数。
参考:http://www.51hei.com/bbs/dpj-40328-1.html
/******************* 补充 ******************/
1、assert_param()宏函数不是仅仅在编译期间检查参数的,而是在任何使用它的地方,任何时刻检查语句的正确性,即在运行时检查的这样的话同时也实现了编译期检查的功能,只要程序员传入了错误的参数,就会立即停止(实际上是进入死循环),通常用来进行参数检查。
2、由1可知,它是生成代码的。
3、如果语句正确,那么不执行任何动作,如果错误,那个调用assert_failed()函数,这个是真正意义上的函数。定义如下:
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)
{
}
}
4、程序调试好后,取消 #define USE_FULL_ASSERT 1 的注释,那么就全速运行了,不再进行任何检查。
问题:#define assert_param(expr) ((void)0) 这是个宏定义,但是我真是不知道(void)0能执行什么操作
具体的定义和用法在下面
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab,u32 Offset)
{
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR=NVIC_VectTab|(Offset&(u32)0x1FFFFF80);
}
答案:这是断言机制。意思是在关闭断言的情况下,
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab,u32 Offset)
{
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR=NVIC_VectTab|(Offset&(u32)0x1FFFFF80);
}
就相当于:
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab,u32 Offset)
{
(void)0; // 不执行任何操作,对程序也没有副作用
(void)0; // 不执行任何操作,对程序也没有副作用
SCB->VTOR=NVIC_VectTab|(Offset&(u32)0x1FFFFF80);
}
由于你的这个断言没贴完整,我就拿VC++6.0下的断言来说明把。
在VC++6.0下的assert.h中:
ifdef NDEBUG
define assert(exp) ((void)0)
else
define assert(exp) (void)( (exp) || (_assert(#exp, FILE, LINE), 0) )
如果你写的代码中使用了断言,比如:
assert(i>=0);
那么这句的在程序中的作用分两种情况:
1. 若果在assert.h被包含之前NDEBUG这个宏未定义,assert(exp) 就被定义为(void)( (exp) || (_assert(#exp, FILE, LINE), 0) )。这样当i<0时,i>=0这个表达式为假,所以程序就会终止。并通知程序员发生错误的文件位置和代码行。但是错误信息仅仅对程序员有用。对用户来说,程序异常终止就显得不是那么友好了(用户此时需要的是挽回错误)。所以在发布给用户的程序中,assert(断言)要关闭。在VC++6.0下这是通过在包含assert.h头文件之前定义NDEBUG实现的。
2.在定义了NDEBUG的情况下,断言不应该给程序带来副作用。这样断言就被定义为:
define assert(exp) ((void)0)
此时,assert(i>=0);不管括号中表达式为真还是为假,这一行代码其实相当于:
NULL;意思是不执行任何操作。
---------------------
作者:fly__chen
来源:CSDN
原文:https://blog.csdn.net/fly__chen/article/details/52927501
版权声明:本文为作者原创文章,转载请附上博文链接!