断言(assert v.)本质上是程序员在代码中写下的 “假设” 或 “承诺”。在程序运行时检查某个条件是否为真。如果条件为真,程序正常执行;如果条件为假,说明程序逻辑出了问题,断言会触发错误内容(比如进入死循环、打印错误信息等)。
一、嵌入式调试中的assert_param() 宏
断言只用于调试阶段,帮助开发人员快速发现程序中的逻辑错误(比如参数非法、状态异常),让代码更易读(比如assert(p != NULL)说明这里假设指针 p 非空)。
断言不能用于处理运行时的正常错误(比如文件打不开、网络断开),这些应该用普通的错误处理逻辑比如返回错误码等。下面这段程序如果断言失败会进入死循环。
// 断言测试的引脚掩码 0000 0000 0000 0000 1111 1111 1111 1111
#define GPIO_PIN_MASK (0x0000FFFFU)
// 检查传入的__PIN__参数是不是一个合法的 GPIO 引脚值:判断参数低16位含非0,高16位全0,输出真
#define IS_GPIO_PIN(__PIN__) ((((uint32_t)(__PIN__) & GPIO_PIN_MASK) != 0x00U) &&\
(((uint32_t)(__PIN__) & ~GPIO_PIN_MASK) == 0x00U))
// 我断言expr为真,否则进入死循环
#define assert_param(expr) if(!(expr)) while(1);
// 调用:检查GPIO引脚是否合法
assert_param(IS_GPIO_PIN(GPIO_PIN_5));
断言核心目的是调试阶段检查参数合法性,发布阶段需要移除检查。
//断言空
#define assert_param(expr) if(!(expr)) ((viod)0U)
二、C语言标准库的assert()宏
此外,C 语言标准库<assert.h>提供了assert()宏,这个 assert 宏的作用是:在程序运行时检查 _Expression表达式是否为真。如果为真,宏等价于空操作;如果为假,会调用 _assert 函数(传入断言失败的表达式、文件名、行号),实现断言失败的错误提示 / 程序终止。
//assert.h文件内的宏
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_assert(#_Expression,__FILE__,__LINE__),0))
#define assert(_Expression) \
(void)((!!(_Expression)) || (_assert(#_Expression,__FILE__,__LINE__),0))
1. !!(_Expression) 强制转换成布尔值:第一个!把表达式结果取反(非 0→0,0→1),第二个!再次取反,最终把任意值转换成纯布尔值(非 0→1,0→0)。
2. || 利用短路特性控制是否执行断言失败逻辑:逻辑或 || 的规则是左边为真时,右边直接跳过不执行;左边为假时,才执行右边。
3. (_assert(#_Expression,__FILE__,__LINE__),0) 断言失败的执行逻辑:
①逗号运算符,先执行 _assert(#_Expression,__FILE__,__LINE__);
②#_Expression:把断言表达式转成字符串(比如 assert(p!=NULL) 传入 "p!=NULL");
③__FILE__/__LINE__:告诉 _assert 函数断言失败发生在哪个文件、哪一行;
④_assert 是底层实现函数(通常由标准库提供),功能是打印错误信息 + 终止程序(比如调用 abort())。
⑤最后返回 0:逗号表达式最终结果为 0,不影响整个 || 表达式的逻辑(此时左边已经是 0,右边返回 0,整体为 0)
4. (void) 消除编译器警告:整个宏的最终结果被强制转换成 void 类型,告诉编译器 “这个表达式的返回值不需要使用”,避免编译器报 “表达式结果未使用” 的警告。
三、为什么使用||不用if语句判断?
assert ()宏被设计成可以放在任意表达式位置(比如赋值、逗号表达式中),但 if 是语句,无法嵌入表达式:
// 合法场景:断言作为表达式的一部分
int a = 5, b = (assert(a>0), 10);
// 用if的宏展开后:
int a = 5, b = (if (!(a>0)) {_assert(...);}, 10);
// 编译器直接报错:if语句不能出现在赋值表达式中!
四、知识点
标准库<assert.h>提供了assert()宏可以使用,HAL库中的assert_param(expr)发布阶段需要((void)0U);
__FILE__:编译器内置宏,代表当前源文件的文件名(字符串);
__LINE__:编译器内置宏,代表当前代码的行号(整数);
#_Expression:宏的 “字符串化” 操作,把传入的表达式转换成字符串(比如 assert(a>0) 会变成 #(a>0) → "a>0");
||:逻辑或运算符,具备短路特性(左边为真时,右边不执行);
(A,B):逗号表达式,执行顺序是先 A 后 B,最终结果为 B 的值。
2718

被折叠的 条评论
为什么被折叠?



