“C语言笔记”assert怎么用?

2020年02月03日12:58:08 科技 1825

一、什么是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语言笔记”assert怎么用? - 天天要闻

这个例子中只有几行代码,我们很快就可以找到程序蹦的原因就是变量c的值为0。但是,如果代码量很大,我们还能这么快的找到问题点吗?

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

“C语言笔记”assert怎么用? - 天天要闻

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

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();}

这样,也可以给我们起到提示的作用:

“C语言笔记”assert怎么用? - 天天要闻

但是,使用assert()至少有几个好处:

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

2)无需要更改代码就能开启或关闭assert机制(开不开启关系到程序大小的问题)。如果认为已经排除了程序的bug,就可以把下面的宏定义写在包含assert.h的位置的前面:

#define NDEBUG

并重新编译程序,这样编辑器就会禁用工程文件中所有的assert()语句。如果程序又出现问题,可以移除这条#define指令(或把它注释掉),然后重新编译程序,这样就可以重新启用了assert()语句。

(2)例子二:STM32库函数

我们来看我们比较熟悉的GPIO初始化函数:

“C语言笔记”assert怎么用? - 天天要闻

可见,该函数的实现中,有三条assert_param()这样的语句,其作用就是对一些函数入口参数进行一些有效性检查。其实assert_param()这就类似与我们C标准库中的assert()。针对stm32f10x系列来说,其被定义在文件stm32f10x_conf.h中:

“C语言笔记”assert怎么用? - 天天要闻

这是一个例子,除了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,编译时就会输出第二个参数的字符串,而且编译不通过。

该程序编译结果如下:

“C语言笔记”assert怎么用? - 天天要闻

可见,编译报错了,并且打印提示了我们的问题所在点,打印出了我们_Static_assert第二个参数的字符串,这样我们就可以很快地定位到导致编译错误的问题了。

以上就是关于assert()断言宏的一些总结笔记,如有错误欢迎指出!

科技分类资讯推荐

Neo7 Turbo对友商下狠手 一加对比真我显单薄 - 天天要闻

Neo7 Turbo对友商下狠手 一加对比真我显单薄

#双11国货出行好物#对比同门品牌一加推出的 Ace 5 竞速版,真我Neo7 Turbo 直接用实力完成了一场“自家人打自家人”的无情对决,具体怎么说,不妨跟着我们一起了解下这款新机吧!
“猴脸识别2.0”上线——不但“辨”猴脸,更“懂”猴子心 - 天天要闻

“猴脸识别2.0”上线——不但“辨”猴脸,更“懂”猴子心

人民网西安5月29日电 (记者李志强)“这只猴子叫‘洋葱’,可信度85%。”打开手机小程序,对准电脑屏幕上的一张猴子照片进行识别,不到5秒钟,结果就出来了。随后的演示图像中,“洋葱”的整个身体被标记上了若干个点位,通过分析这些点位的运动规律,就能深度解析它的行为意图。 5月29日,记者从西北大学获悉,此前研发出“...
AI眼镜将取代手机?对话因眼镜提词器走红的杭州公司联创 - 天天要闻

AI眼镜将取代手机?对话因眼镜提词器走红的杭州公司联创

戴着一副透明的眼镜,你可以在路上导航、脱稿在台上演讲,同时还能启动声纹支付、语音唤起AI点餐和打车,这些充满科幻感的场景已经被智能眼镜头部厂商Rokid(杭州灵伴科技有限公司)实现了。Rokid即将在6月上线新一代智能眼镜Rokid Glasses。伴随AI关键技术的发展,尤其是大模型的快速推进,AI眼镜有望成为下一代人机交互的...
黄仁勋:无论有没有美国芯片,中国AI发展都会继续 - 天天要闻

黄仁勋:无论有没有美国芯片,中国AI发展都会继续

5月29日,英伟达发布了第一财季财报。随后,英伟达CEO黄仁勋、CFO克莱特·克罗斯出席电话会议。会上,谈到美国芯片出口管制对英伟达的影响,黄仁勋表示,出口管制对英伟达造成了巨大的损失,但这并不能阻碍中国人工智能的发展,反而会刺激中国的创新,增强中国在海外的实力,削弱美国的地位。他认为,AI竞赛不仅仅是关于芯...
川南大数据中心即将投运 政府搭台促成企业间合作 - 天天要闻

川南大数据中心即将投运 政府搭台促成企业间合作

封面新闻记者 黄晓庆5月28日下午,内江市举行“云上甜城·算网为先”川南大数据中心助力内江发展新质生产力推介会。记者从会上获悉,即将投入运营的川南大数据中心总投资4亿元、设计机架500个、算力超过10000VCPU、存储能力50PB,是四川
当手机叫车融入大众日常,出租车为何开始到处排队趴活? - 天天要闻

当手机叫车融入大众日常,出租车为何开始到处排队趴活?

在商场边,打出租车似乎越来越好打了,而且直接上车就能走。笔者近日在广州深圳等地,感受到了街头打出租的变化。一个工作日的晚7点半左右,广州北京路天河城门口,停着几辆出租车,车顶的“空车”灯在暮色中格外醒目。司机阿强摇下车窗,望着匆匆而过的行人:“三年前这里根本不担心没活,现在蹲半小时能拉一单就算快了。...
数量锐减30万台,ATM机借势AI卷土重来 - 天天要闻

数量锐减30万台,ATM机借势AI卷土重来

在移动支付全国普及的今天,当你路过街角的ATM机时,是否曾疑惑:这些金属柜体是否终将沦为数字时代的遗迹?最新数据揭示了一个反直觉的现实,2024年全国ATM保有量降至80.27万台,较峰值缩减近30万台,但每台设备的价值正经历前所未有的重构。这场静默的变革背后,是一场关乎金融普惠与科技革新的生死蜕变。ATM行业的冰火两...
大模型牵手手机银行,开启无菜单时代|银行APP测评 - 天天要闻

大模型牵手手机银行,开启无菜单时代|银行APP测评

2025年上半年,银行依旧以将大模型装进手机银行作为要务,六大行率先对其大模型进行了升级优化,例如工商银行在同业中率先发布企业级千亿金融大模型技术体系 “工银智涌”,累计调用量超 10 亿次,赋能 20 多类业务、200 余个场景,较 2024 年在应用场景数量和调用量上有显著提升,其大模型技术能力在同业中率先输出至中小...