前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【实用编程技巧】不想改bug?初学者必须学会使用的报错函数assert!(断言函数详解)

【实用编程技巧】不想改bug?初学者必须学会使用的报错函数assert!(断言函数详解)

作者头像
修修修也
发布2024-04-01 15:38:56
1330
发布2024-04-01 15:38:56
举报
文章被收录于专栏:修也的进阶日记

🦄个人主页:修修修也 🎏所属专栏:程序调试及报错解决 ️操作环:Visual Studio 2022

相信大家在编程过程中常常会遇到这种情况:信心满满写好了一个程序,结果一运行:

是不是心脏骤停的感觉?于是埋头苦改程序,但找了一遍又一遍还是找不到问题出在哪.

下定决心拿着自己的代码去找班里的大牛同学,只见大牛同学云淡风轻的在程序里加了一行高深莫测的代码:

代码语言:javascript
复制
assert(ps->size>0);

接着一敲运行,程序弹出了这样一个窗口:

大牛看后得意一笑,对你说道:

看SeqList.c文件的第61行,你的线性表都删的一个元素都没了还在删,不出错等啥啊?

你挠挠头,原来是这里出错了,但自己为啥找了一上午都没找到,而大牛随便敲了一行代码就把bug找出来了.

你下定决心要向大牛请教,求他教你这个能定位报错,秒找bug的"神功".

大牛嘿嘿一笑,说:"这有啥,我只不过是用了一个assert()函数而已".你想学的话,我就给你讲讲吧.


一.assert()函数简介

我们先来看一下cplusplus.com - The C++ Resources Network网站上assert()函数的基本信息:

大致翻译一下就是:

评估断言 如果带有函数形式的宏的参数表达式等于零(即表达式为假),则向标准错误设备写入一条消息,并调用abort函数终止程序执行。 显示的消息(即错误信息)具体内容取决于特定的库实现,但至少应包括:断言失败的表达式源文件的名称以及发生错误的行号。通常的表达式格式为: Assertion failed: expression, file filename, line line number (断言失败:表达式,文件名,行号) 如果在包含<assert.h>时已经定义了名为NDEBUG的宏,则禁用此宏。这允许在调试程序时,在源代码中包含任意数量的assert调用,然后通过简单地在代码开始之前包含一行像这样的代码来禁用所有assert调用#define NDEBUG 因此,此宏旨在捕获编程错误,而不是用户或运行时错误,因为在程序退出调试阶段后通常会禁用它

1.函数功能

可以看到,assert()函数的功能是:

计算表达式 expression(即参数),如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。

2.函数参数

该函数一共有1个参数,是:

代码语言:javascript
复制
void assert (int expression);
🎏int expression

参数的类型是整形(int),它是一个结果为整形的表达式,它作为是否符合断言条件的判断依据.

3.函数返回值

函数的返回值类型是空(void),它表示该函数运行结束后不需要返回值.

4.函数头文件

该函数包含在头文件<assert.h>中.


二.assert()函数的用法总结及注意事项

1.使用assert()函数在函数开始处检验传入参数的合法性

assert()函数的使用场景是:当我们想在函数开始时检验传入参数的合法性时,我们可以使用assert()函数来实现这一诉求.

如下,在顺序表的定点插入函数中,我们使用assert()函数判断顺序表传入的参数是否合理,即顺序表地址是否为NULL?以及要插入的元素插入的范围是否在顺序表的元素范围内?

分别给assert()函数传入:顺序表的地址(即ps).

pos>=0(即判断插入点pos是否在0号元素之前),

pos<=ps->size(即判断插入点是否在最后一个元素的next之外).

程序如下:

代码语言:javascript
复制
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
    //判断ps指针不为NULL
	
	assert(pos >= 0 && pos <= ps->size);
    //定点插入,前提是插入点必须在0~size之间
	//pos==0,相当于头插;pos==ps->size,相当于尾插

	SLCheckCapacity(ps);
    //插入前判断是否需要扩容

	int end = ps->size - 1;  //从最后一个数据元素开始依次向后移一格
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;     //最后在pos位置插入目标元素
	ps->size++;         //表中数据元素+1
}

接下来我们尝试在大小为4的顺序表中在第20位插入一个5:

代码语言:javascript
复制
SLInsert(&s, 20, 5);

在vs编译器中运行查看结果:

可见assert()函数成功的报出了这个错误,报错格式如下:

断言失败:表达式(pos>=0&&pos<=ps->size),文件名 D:\bit108\SeqList\SeqList.c,行号 119

根据这一信息,我们很容易就知道要去检查程序中传入的pos参数是否符合这一范围,然后解决这一bug.


2.每个assert只检验一个条件

现在我们还面临一个问题,就是不知道到底是因为pos<0导致程序出现bug,还是因为pos>ps->size导致程序出现bug.

也即,虽然使用了assert函数来报错,但同时检测多个条件时,断言失败,我们还是无法清楚的立即知道到底是哪个条件出错了,所以我们应当在使用assert函数时让它只检测一个条件.

如:

代码语言:javascript
复制
assert(ps);
assert(pos >= 0 );
assert(pos <= ps->size);

3.不能使用改变环境的语句

因为assert只在DEBUG生效,如果这么做,会使用程序在真正运行时遇到问题.

错误示例:

代码语言:javascript
复制
 assert(i++ < 100)

这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。

正确示例:

代码语言:javascript
复制
assert(i<100)
i++;
4.有的地方,assert不能代替条件过滤

程序一般分为Debug 版本和Release 版本,Debug 版本用于内部调试,Release 版本发行给用户使用。

断言assert 是仅在Debug 版本起作用的宏,它用于检查"不应该"发生的情况,如果编译为Release版本则被忽略.

因为使用断言是捕捉不应该发生的非法情况,而这种情况一旦发生就要立即终止程序来做出相应处理.

如果我们的目的只是需要将程序限定在一些范围内运行,则应该使用类似if...else...这类语句来实现.

5.使用断言的几个原则

  1. 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
  2. 使用断言对函数的参数进行确认。
  3. 在编写函数时,要进行反复的考查,并且自问:"我打算做哪些假定?"一旦确定了的假定,就要使用断言对假定进行检查。
  4. 一般教科书都鼓励程序员们进行防错性的程序设计,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果"不可能发生"的事情的确发生了,则要使用断言进行报警。
6.assert的缺点

使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销

在调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下:

代码语言:javascript
复制
#include 
#define NDEBUG 
#include

结语

在学会了使用assert()函数后,我们日常编写的代码就具有了一定的健壮性.如果不使用assert,出错了程序只会陷入死循环,然后挂掉.但是有了assert的辅助,我们就可以马上找出准确错误地址以及原因.希望这篇关于assert宏的介绍能对大家有所帮助,学海漫浩浩,我亦苦作舟!

大家一起学习,一起进步!

相关文章推荐 有关“函数用于调用的参数太少”问题解决办法 【数据结构】线性表的顺序存储结构(顺序表详解) 有关“Run-Time Check Failure #2 - Stack around the variable ‘arr‘ was corrupted.“问题解决办法 【C语言】qsort()函数详解:能给万物排序的神奇函数



本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.assert()函数简介
    • 1.函数功能
      • 2.函数参数
        • 🎏int expression
      • 3.函数返回值
        • 4.函数头文件
          • 1.使用assert()函数在函数开始处检验传入参数的合法性
          • 2.每个assert只检验一个条件
          • 3.不能使用改变环境的语句
          • 4.有的地方,assert不能代替条件过滤
          • 5.使用断言的几个原则
          • 6.assert的缺点
      • 二.assert()函数的用法总结及注意事项
      • 结语
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档