【C语言笔记】assert()怎么用?

一、什么是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来判断,这是有必要的*/
{
/* 做一些处理 */
}

下面看一下assertif做防错处理的两点用法的区别:

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(

(0)

相关推荐

  • C语言如何使用断言避免踩坑

    何为断言 断言一般是用于检测在某个程序位置程序必须满足某些条件的宏.一般用的多的可以分两种种情况: 前置条件:在某个程度点开始的地方 后置条件:在某段程序执行结束后,一般用于检测执行结果 断言发生表示 ...

  • 代码安全性和健壮性:如何在if和assert中做选择?

    嵌入式软件开发,实际项目通常会考虑代码的一些安全性和健壮性,一般来说,需要添加一些"检测"的代码. 下面就来为大家分享一下关于if和assert的内容. 一.前言 我们在撸代码的时 ...

  • C语言程序里全局变量、局部变量、堆、栈的存储区域

    文章目录 一.存储区域介绍 二.在ubuntu系统中输出信息进行验证 三.在Keil中针对stm32系统进行验证 四.总结 五.参考资料 一.存储区域介绍 C语言在内存中一共分为如下几个区域 区域 作 ...

  • C语言笔记-双向链表和循环链表

    两种链表的增删改查操纵类似于单向链表. 双向链表: 一种更复杂的链表是"双向链表"或"双面链表".每个节点有两个连接:一个指向前一个节点,(当此"连接 ...

  • 辛苦整理的C语言笔记,还好没放弃

    c语言入门 C语言一经出现就以其功能丰富.表达能力强.灵活方便.应用面广等特点迅速在全世界普及和推广.C语言不但执行效率高而且可移植性好,可以用来开发应用软件.驱动.操作系统等.C语言也是其它众多高级 ...

  • 【C语言笔记】时间日期函数

    常持正念方圆梦,不忘初心总是春. /********前言********/ time.h是C/C++中的日期和时间头文件.用于需要时间方面的函数.下面分享time.h头文件中几个常用函数的用法: /* ...

  • 【C语言笔记】关于随机数的总结

    C语言的库头文件stdlib.h中有个生成随机数的函数: int rand(void); 该函数返回0~RAND_MAX之间的随机数,在stdlib.h中可知道,RAND_MAX为0x7FFF,如: ...

  • 【C语言笔记】变参函数

    提到变参函数,我们的感觉是不是既熟悉又陌生 ?感觉熟悉是因为我们平时都在使用着,如我们常使用的printf()函数与scanf()函数就是典型的变参函数.因为printf()函数是变参函数我们才可以根 ...

  • 【C语言笔记】ASCII码可见字符与不可见字符

    ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧 ...

  • 【C语言笔记】什么是ANSI C标准?

    我们在很多地方都有看到过K&R C.ANSI C.ISO C.C89.C99.C11等标准,具体有什么不同呢?(文末有个很好的实例) 什么是K&R C? 1978年,丹尼斯·里奇(De ...

  • 【C语言笔记】如何查看数据类型范围?

    知识点一:查看整数范围 当前的编译环境下,你可能不知道int的数据范围是多少,或者记不清无符号短整型的范围是0~65535还是0~65536?这时候就可以按照如下程序进行输出查看: #include ...

  • 【C语言笔记】分享一个C语言测试程序模板

    前言 平时需要测试一些比较模糊的知识点,或则想要验证一些函数时,我们常常会建一个test.c文件,然后在这个文件里写我们的测试代码,测试完毕后常常会删掉该文件.下次再遇到同样的问题的时候,可能又是记不 ...