一。什么是assert()?
编写代码时。我们总是会做出一些假设。断言(assert)就是用于在代码中捕捉这些假设。可以将断言看作是异常处理的一种高级形式。
断言表示为一些布尔表达式。程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证。因此可以在测试时启用断言。而在部署时禁用断言。同样。程序投入运行后。最终用户在遇到问题时可以重新启用断言。
注意assert()是一个宏。而不是函数。
二。assert怎么用?
1。assert所在的头文件及原型
在MinGW工具中。assert()宏在存在于头文件assert.h中。其关键内容如下:
#ifdef NDEBUG#define assert(x)((void)0)#else /* debugging enabled */_CRTIMP void __cdecl __MINGW_NOTHROW _assert (const char*。 const char*。 int) __MINGW_ATTRIB_NORETURN;#define assert(e) ((e) ? (void)0 : _assert(#e。 __FILE__。 __LINE__))#endif/* NDEBUG */
assert()宏接受一个整形表达式参数。如果表达式的值为假。assert()宏就会调用_assert函数在标准错误流中打印一条错误信息。并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。
当我们认为已经排除了程序的bug时。就可以把宏定义#define NDEBUG写在包含assert.h位置前面。
小知识:
__cdecl是C Declaration的缩写(declaration。声明)。表示C语言默认的函数调用方法:所有参数从右到左依次入栈。_CRTIMP是C run time implement的简写。C运行库的实现的意思。作为用户代码。不应该使用这个东西。提示是使用dll的动态 C 运行时库还是静态连接的 C 运行库的一个宏。
#ifndef _CRTIMP#ifdef _DLL#define _CRTIMP __declspec(dllimport)#else /* ndef _DLL */#define _CRTIMP#endif /* _DLL */#endif /* _CRTIMP */
__MINGW_NOTHROW与__MINGW_ATTRIB_NORETURN是异常处理相关标识
这几个标识符在C语言标准库文件中都有用得到。但是我们不需要关心。在我们用户的角度来看。以上函数原型我们看成:void _assert(const char*。 const char*。 int);即可。
2。assert应用
assert主要用于类型检查及单元测试中。
单元测试(unit testing)。是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义。一般来说。要根据实际情况去判定其具体含义。如C语言中单元指一个函数。
(1)例子一:除法运算
/*编译工具:mingw32 gcc6.3.0*/#include <stdio.h>#include <assert.h>int main(void){int a。 b。 c;printf("请输入b。 c的值:");scanf("%d %d"。 &b。 &c);a = b / c;printf("a = %d"。 a);return 0;}
此处。变量c作为分母是不能等于0。如果我们输入2 0。结果是什么呢?结果是程序会蹦:
这个例子中只有几行代码。我们很快就可以找到程序蹦的原因就是变量c的值为0。但是。如果代码量很大。我们还能这么快的找到问题点吗?
这时候。assert()就派上用场了。以上代码中。我们可以在a = b / c;这句代码之前加上assert(c);这句代码用来判断变量c的有效性。此时。再编译运行。得到的结果为:
可见。程序蹦的同时还会在标准错误流中打印一条错误信息:
Assertion failed:c。 file hello.c。 line 12
这条信息包含了一些对我们查找bug很有帮助的信息:问题出在变量c。在hello.c文件的第12行。这么一来。我们就可以迅速的定位到问题点了。
这时候细心的朋友会发现。上边我们对assert()的介绍中。有这么一句说明:如果表达式的值为假。assert()宏就会调用_assert函数在标准错误流中打印一条错误信息。并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。
所以。针对我们这个例子。我们的assert()宏我们也可以用以下代码来代替:
if (0 == c){puts("c的值不能为0。请重新输入!");abort();}
这样。也可以给我们起到提示的作用:
但是。使用assert()至少有几个好处:
1)能自动标识文件和出问题的行号。
2)无需要更改代码就能开启或关闭assert机制(开不开启关系到程序大小的问题)。如果认为已经排除了程序的bug。就可以把下面的宏定义写在包含assert.h的位置的前面:
#define NDEBUG
并重新编译程序。这样编辑器就会禁用工程文件中所有的assert()语句。如果程序又出现问题。可以移除这条#define指令(或把它注释掉)。然后重新编译程序。这样就可以重新启用了assert()语句。
(2)例子二:STM32库函数
我们来看我们比较熟悉的GPIO初始化函数:
可见。该函数的实现中。有三条assert_param()这样的语句。其作用就是对一些函数入口参数进行一些有效性检查。其实assert_param()这就类似与我们C标准库中的assert()。针对stm32f10x系列来说。其被定义在文件stm32f10x_conf.h中:
这是一个例子。除了GPIO初始化函数之外。STM32固件库函数中的其他函数都是会做这样的参数检查。
三。assert与if的比较?
assert()断言功能好像用if也能实现。仔细一看这两者还是有区别。下面看一下它们的区别:
先看一个例子。我们使用malloc函数定义一个存着堆空间中的变量。我们该怎么定义及该怎么做一些防御处理呢?
首先。我们要知道。malloc函数如果分配成功内存则返回指向被分配内存的指针(此存储区中的初始值不确定)。否则返回空指针NULL。看如下代码:
int* p = (int*)malloc(sizeof(int));assert(p);/* 错误示例 */
这么写会有问题吗?
看似没问题。但是问题很大!我们的assert()会在我们调试完毕之后禁用掉。这么一来以上代码就相当于只有下面这一句了:
int* p = (int*)malloc(sizeof(int));
此时。当我们的程序在跑的时候malloc申请不到内存空间了。也没有做一些解决措施。可能就会产生致命错误。
我们应该把以上代码改写为:
int* p = (int*)malloc(sizeof(int));if (NULL == p) /*请使用if来判断。这是有必要的*/{ /* 做一些处理 */}
下面看一下assert与if做防错处理的几点用法区别:
1。assert语句用在debug版本的调试中;if(NULL!=p)是在release版本中检验指针的有效性;
2。assert一般用与检查函数参数的合法性(有效性)而不是正确性。但是合法的程序并不见得是程序逻辑正确的程序。该用if做判断处理的地方还是得做处理。
也就是assert在调试期间用来检查一些不允许出现的情况是否有发生。一旦发生就表明我们的程序很可能有BUG。而if判断的就是我们理所应当处理的各种情况。且这些情况如果发生并不代表程序发生BUG。
四。_Static_assert(C11标准)
assert()是在运行时进行检查的。如果一份工程很大。编译起来需要很长时间。一些情况在运行时检查。效率就比较低了。
这时候_Static_assert()就派上用场了。这是C11标准中的一个特性。_Static_assert()在编译时进行检查。如果编译时检测到代码里的一些异常情况。就会导致程序无法通过编译。下面来看一个例子:
/*编译环境:mingw32 gcc6.3.0编译命令:gcc -std=c11 hello.c -o hello.exe*/#include <stdio.h>#include <limits.h> /*CHAR_BIT是limits.h中的一个宏*/_Static_assert(CHAR_BIT == 16。 "16-bit char falsely assumed");int main(void){printf("欢迎关注嵌入式大杂烩!查看更多笔记\n");return 0;}
_Static_assert接受两个参数。第一个参数是整型常量表达式。第二个参数是一个字符串。如果第一个表达式为0。编译时就会输出第二个参数的字符串。而且编译不通过。
该程序编译结果如下:
可见。编译报错了。并且打印提示了我们的问题所在点。打印出了我们_Static_assert第二个参数的字符串。这样我们就可以很快地定位到导致编译错误的问题了。
以上就是关于assert()断言宏的一些总结笔记。如有错误欢迎指出!
本文地址:https://gpu.xuandashi.com/77920.html,转载请说明来源于:渲大师
声明:本站部分内容来自网络,如无特殊说明或标注,均为本站原创发布。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。分享目的仅供大家学习与参考,不代表本站立场!