程序的调试排错往往会占用整个工作的绝大多数时间,很多高级语言都拥有一整套的调试方法。C 语言虽然比较底层,但在调试程序时,也可以使用我之前推荐的 GDB等调试工具,但很多高手绝对不会仅依赖于调试工具,大多数人还是喜欢在开发时用 assert 来排查错误,而开发结束后,更有一些人会使用更加高效完整的日志库等来监控可能出现的 bug。
C 标准库
C 标准库中自带了 assert.h 这个文件,可以说前人在使用时已经将排错考虑细致了,我们加入这个功能后即可以直接使用 assert 进行调试了,函数声明:void assert(int expression);
expression 这可以是一个变量或任何 C 表达式。如果 expression 为 TRUE,assert() 不执行任何动作。如果 expression 为 FALSE,assert() 会在标准错误 stderr 上显示错误消息,并中止程序执行。实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <assert.h> #include <stdio.h> int main(void) { int a; printf("请输入一个整数值: "); scanf("%d", &a); assert(a >= 10); printf("输入的整数是: %d\n", a); } 请输入一个整数值: 2 a: a.c:22: main: Assertion `a >= 10' failed. [1] 9632 abort (core dumped) ./a
|
网上查了下,没能找到 assert 标准库的函数原型,于是自己写了个简易版本的 assert :
1 2 3 4 5 6 7
| #define assert(expr) ((void)((expr) || (_assert(__FILE__,__LINE__),0))) void _assert(const char *file, int line) { printf("%s:%d", file, line); exit(0); }
|
MCU 自带库 assert 实现
这边我们以国内广泛使用的 stm32 v3.5.0 官方库为例,使用STM32库函数的时候,你会发现带参数的库函数前面都有assert_param语句。
assert_param语句是用于程序开发的时候,调试用的检测语句。默认是不开启的,你可以无视它的存在。但是,当你在调试程序的时候,可以打开这个检测机制,调试完了再关闭。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /* Exported macro /** * @brief The assert_param macro is used for function's parameters check. * @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. * @retval None */ /* Exported functions void assert_failed(uint8_t* file, uint32_t line);
|
如果我们想要使用 assert_param 就需要定义加入 #define USE_FULL_ASSERT
语句,另外我们可以发现 assert 判定失败时会调用 assert_failed() 函数,但值得注意的是, ST 的固件库中没有预留这个函数的原型,因此我们需要手动创建这个 assert_failed 函数,以下提供一个可供参考的代码:
1 2 3 4 5 6 7 8 9
| void assert_failed(uint8_t* file, uint32_t line) { 注意:编译器 Build Output 栏是不会报错的,这边使用的是 MCU 数据如何输出, 需要根据具体情况分析,一般 MCU 都会把结果输出到串口。 */ printf("Wrong parameters value: file %s on line %d\r\n", file, line); while(1); }
|
另外 assert_failed()/_assert 函数基本上是所有判定失败后都需要调用的函数,具体要根据你平台提供的库而定,有些 MCU 平台是预留了 assert_failed 这种函数的,你不需要重新定义,只需要略加补充方法即可。例如 EFM32 提供的 assert 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #if defined(DEBUG_EFM) || defined(DEBUG_EFM_USER) void assertEFM(const char *file, int line); #define EFM_ASSERT(expr) ((expr) ? ((void)0) : assertEFM(__FILE__, __LINE__)) #else #define EFM_ASSERT(expr) ((void)(expr)) void assertEFM(const char *file, int line) { (void)file; (void)line; while (true) { } }
|
小结
assert 调用基本上大同小异,错误时直接终止程序或者死循环,同时能够提供代码出错位置即可。开发时,不适用 assert 也是可以满足绝大多数需求的,但这时你往往需要自己写语句让错误输出出来,assert 仅是提供了一个更加便捷的操作方式而已。
参考链接:
http://www.runoob.com/cprogramming/c-macro-assert.html
http://www.rationmcu.com/stm32/1508.html
写作时间:21:38-22:17