[转]理解宏:#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))

 

最近调试程序,发现ST的固件库中到处都是assert_param()函数,便查了一下,做个笔记。

assert_param语句的作用?

assert_param语句是用于程序开发的时候,调试用的检测语句,帮助程序员始终选择有效的参数。默认是不开启的,可以无视它的存在。但是,当你在调试程序的时候,可以打开这个检测机制,调试完了再关闭。

所谓有效的参数是指满足规定范围的参数,比如某个参数的取值范围只能是小于3的正整数,如果给出的参数大于3,则这个assert_param()可以在编译时报告错误,使程序员可以及时发现错误,而不必等到运行程序时,因为程序运行错误而大费周折。

它确实在程序的运行上牺牲了效率(但只是在调试阶段),但在项目的开发上却帮助你提高了效率。

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

assert_failed源代码:

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

在spi.c中调用assert_param():

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
版权声明:本文为作者原创文章,转载请附上博文链接!

你可能感兴趣的:(STM32学习)