assert

一、什么是assert()?

写代码的时候,我们总会做一些假设。断言用于在代码中捕捉这些假设。断言可以被视为异常处理的高级形式。

断言被表达为布尔表达式,程序员认为在程序的某一点上是正确的。断言验证可以随时启用和禁用,因此断言可以在测试时启用,在部署时禁用。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。

注意,assert()是一个宏,而不是一个函数。

二、assert怎么用?1、assert所在的头文件及原型

在MinGW工具中,assert()宏存在于头文件assert.h中,其关键内容如下:

# ifdef NDEBUG # define assert(x)((void)0)# else/*调试启用*/_ Cr TIMP void _ _ cdecl _ _ MINGW _ NOTHROW _ assert(const char*,const char *,int)_ MINGW _ ATTRIB _ no return;#定义assert(e) ((e)?(void) 0: _ assert (# e,_ _ file _ _,_ _ line _ _)# endif/* ndedebug */assert()宏接受整数表达式参数。如果表达式的值为false,assert()宏将调用_assert函数在标准错误流中打印错误消息,并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。

当我们认为已经消除了程序的bug,就可以在包含assert.h的位置前写宏定义#define NDEBUG。

小知识:

__cdecl是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈。_CRTIMP是C run time implement的简写,C运行库的实现的意思。作为用户代码,不应该使用这个东西。提示是使用dll的动态 C 运行时库还是静态连接的 C 运行库的一个宏。

# ifndef _ Cr TIMP # ifdef _ DLL # define _ Cr TIMP _ _ declspec(dllimport)# else/* ndef _ DLL */# define _ Cr TIMP # endif/* _ DLL */# endif/* _ Cr TIMP */_ _ MINGW _ NOTHROW和__MINGW_ATTRIB_NORETURN与异常处理相关。这些标识符在C语言标准库文件中都很有用,但我们不需要在意。从我们用户的角度,我们把上面的函数原型看作void _ assert (constchar *,constchar *,int);去做吧。

2、assert应用

Assert主要用于类型检查和单元测试。

单元测试是指检查和验证软件中最小的可测试单元。一般来说,单元测试中一个单元的含义要根据实际情况来判断,比如在C语言中,一个单元指的是一个函数。

(1)例1:除法运算

/*编译器:mingw32gcc 6.3.0 */# include

assert

这个例子只有几行代码,我们很快就能发现程序跳转的原因是变量c的值为0。但是,如果代码量很大,还能这么快发现问题吗?

这时候assert()就派上用场了。在上面的代码中,我们可以用a = b/c;在此代码前添加assert(c );这段代码用来判断变量c的有效性,此时再次编译运行,结果是:

可见,程序还会同时在标准错误流中打印一条错误消息:

断言失败:c,文件hello.c,第12行

这些信息包含了一些非常有助于我们找到bug的信息:问题出在hello.c文件的第12行的变量c中。这样就可以快速定位问题点。

这时,细心的朋友会发现,在我们对assert()的介绍中,有这样一句话,如果表达式的值为false,assert()宏会调用_assert函数在标准错误流中打印一条错误信息,并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。

因此,对于我们的示例,我们也可以用下面的代码替换我们的assert()宏:

if(0 = = c){ puts(& # 34;c值不能为0,请重新输入!");abort();}这样,它也可以给我们一个提示:

然而,使用assert()至少有几个优点:

1)能自动识别文件和问题的行号。

assert机制可以在不改变代码的情况下开启或关闭(开启与否与程序大小有关)。如果你认为程序的bug已经消除了,可以在包含assert.h的位置前面写以下宏定义:

#定义NDEBUG并重新编译程序,这样编辑器就会禁用项目文件中的所有assert()语句。如果程序再次出现问题,您可以移除这个#define指令(或者将其注释掉)并重新编译程序,以便可以重新启用assert()语句。

(2)例2: STM32库函数

让我们看看熟悉的GPIO初始化函数:

可以看到,在这个函数的实现中,有assert_param()等三条语句,其作用是检查一些函数入口参数的有效性。实际上assert_param()这类似于我们C标准库中的assert()。对于stm32f10x系列,它在文件stm32f10x_conf.h中定义:

这是一个例子。除了GPIO初始化函数,STM32固件库函数中的其他函数都会进行这样的参数检查。

三、assert与if的比较?

Assert()断言函数好像是用if实现的,但两者还是有区别的。让我们来看看它们的区别:

我们先来看一个例子。我们使用malloc函数来定义一个存储在heap 空中的变量。我们怎么定义,怎么做一些防御性的处理?

首先我们要知道malloc函数如果分配内存成功(这个存储区的初始值不确定)就返回一个指向分配内存的指针,否则返回一个空指针。请看下面的代码:

int * p =(int *)malloc(sizeof(int));断言(p);/*错误示例*/这个写法有问题吗?

看似没问题,其实问题很大!我们的assert()在调试后会被禁用,所以上面的代码相当于下面这句话:

int * p =(int *)malloc(sizeof(int));此时,当我们的程序运行时,malloc无法申请内存空并且不做一些解决方案,这可能会导致致命错误。

我们应该将上面的代码重写如下:

int * p =(int *)malloc(sizeof(int));If (NULL == p) /*请用If来判断,哪个是必要的*/{/*做一些处理*/}

让我们来看看assert和if在防止错误方面的一些用法差异:

1.assert语句用于调试调试版本;if(NULL!=p)是检查发布版本中指针的有效性;

2.assert一般用于检查函数参数的合法性(有效性)而不是正确性,但合法的程序不一定是程序逻辑正确的程序,该用if进行判断处理的地方还是要处理。

也就是说,assert是用来检查调试过程中是否出现了一些不允许出现的情况。一旦它们发生,就表明我们的程序很可能有bug,如果判断出我们应该处理的各种情况,如果这些情况发生,并不意味着程序有bug。

四、_Static_assert(C11标准)

运行时检查Assert()。如果一个项目很大,编译的时间会很长,有些情况会在运行时检查,效率会很低。

这时候_Static_assert()就派上用场了,这是C11标准的一个特性。_Static_assert()在编译时检查,如果在编译时检测到代码中的一些异常,程序将无法编译。让我们看一个例子:

/*编译环境:mingw32 gcc6.3.0编译命令:gcc-STD = c11hello.c-ohhello.exe */# include

该程序的编译结果如下:

可以看到报错编译,打印提示我们问题的点,打印我们第二个参数_Static_assert的字符串,这样就可以快速定位导致编译错误的问题。

以下是关于assert()断言宏的一些概要说明。有错误请指出!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论