C语言——调试技巧

31 篇文章 2 订阅

目录

导语:

思维导图:

1、什么是bug?

2、调试是什么?

2.1 调试的基本步骤

2.2 Debug和Release的介绍

3、Windows环境调试介绍 

3.1 调试环境准备

3.2 学会快捷键

3.3 调试的时候查看程序当前信息

3.3.1查看临时变量的值

3.3.2查看内存信息

3.3.3查看调用堆栈

3.3.4查看汇编信息

3.3.5查看寄存器信息

4、调试实例

4.1 实例1

4.2 实例2

5、如何写出好的代码

5.1 优秀的代码

5.2 const的作用

6、编程常见的错误 

6.1 编译型错误

6.2 链接型错误

6.3 运行时错误 

结语:


导语:

调试是非常重要的一个技能,我们在平时写代码的过程中,难免会写出bug,写bug是很正常的事情,我们可以通过调试把bug修复,那样就没什么问题了,如果自己没找到bug,用的时候让别人找到了,那还是有点尴尬的。那么接下来,我们就来学习如何解决我们代码里的bug。(本篇文章使用的环境为:Windows,IDE为:VS2022)

思维导图:

1、什么是bug?

还记得小学的时候,还不知道bug的读音,玩游戏的时候,会说我们来卡“币悠只因”(读字母),然后就等着被踢出对局(如下图),当时也不懂什么是bug。

那什么是bug呢?就是计算机的程序错误,像这种找游戏的漏洞作弊,其实就游戏的bug,当时技术不成熟,bug一大堆,不过现在基本上都修复了,而且现在游戏的bug也很少了,这也说明程序员的水平在不断的提高。

让我们来看看历史上的第一个bug是什么样的吧(下图):

我们历史上的第一台计算机是非常之大的,放在一个房子里面,平时检查机器是否正常工作就是进入房子里检查,有一天值班人员发现机器发生了故障,结果排查发现有一只臭虫卡在二极管上了,影响了正常运行,当天值班员就把这只臭虫贴在了日志上面,从此以后我们就把这种影响电脑正常工作的臭虫引申为bug(这只臭虫,就真的“臭名远扬”了)。

2、调试是什么?

在之前的文章里面,给大家展示程序如何运行的,其实就用到了调试。

所以事件发生必将有迹可循,如果问心无愧,就不需要掩盖也就没有迹象,如果问心有愧,就必然要掩盖,那么就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。

顺着这条途径顺流而下就是犯罪,逆流而上就是真相。

一名优秀的程序员是一名出色的侦探,那么每一次调试的过程,也就是破案的过程(这话说的可真棒,不是我说的,是引用)。

调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序 错误的一个过程。

2.1 调试的基本步骤

①发现程序错误的存在(三类人员:程序员、测试人员、用户)


②以隔离、消除等方式对错误进行定位
③确定错误产生的原因
④提出纠正错误的解决办法
⑤对程序错误予以改正,重新测试

2.2 Debug和Release的介绍

Debug通常称为调试版本它,包含调试信息,并且不作任何优化,便于程序员调试程序;

Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小运行速度上都是最优的,以便用户很好地使用。

因为Debug里面包含调试信息,所以Debug版本内存是要更大一些的,Release不含调试信息,所以内存会小很多。 

3、Windows环境调试介绍 

3.1 调试环境准备

在环境中选择Debug选项,才能使代码正常调试(如图)

3.2 学会快捷键

调试常用快捷键:

F5: 启动调试,经常用来直接跳到下一个断点处

F9: 创建和取消断点

F10: 逐过程

F11: 逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)

Ctrl+F5: 开始执行不调试

Shift+F5: 停止调试

Ctrl+Shift+F5: 重启调试

Ctrl+F9: 停止断点

Ctrl+Shift+F9: 删除所以断点
Ctrl+F10: 运行到光标处

F5(启动调试)与F9(断点的创建与取消)的配合使用

F10(逐语句)与F11(逐过程) 

3.3 调试的时候查看程序当前信息

声明:先调试(F9/F10),才能看到信息!!!

3.3.1查看临时变量的值

3.3.2查看内存信息

3.3.3查看调用堆栈

先进后出,后进先出

3.3.4查看汇编信息

3.3.5查看寄存器信息

当然了,这些只是一些操作方法,我们还得通过日后更多的实践。初学者对于写代码可能感觉会比较吃力,没有调试的意识,可能80%时间在写代码,20%时间在调试;但程序员可能20%时间写代码,80%时间调试。不过相信会认真看这篇文章的人,是有一定的代码能力,所以希望会自己调试找bug,多多实践,那么接下来,我们也通过几个例子,让大家感受更加直接。

4、调试实例

4.1 实例1

实现代码:求 1! + 2! + 3! ... + n!;不考虑溢出

代码如下:

int main()
{
	int i = 0;
	int sum = 0;
	int n = 0;
	int ret = 1;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}
输入:3
输出:15

我们输入的是3,那么就是求3!+2!+1!的值,理应输出9,但程序输出的结果为15
???什么情况???
嘘,别慌,不要声张,我们自己写的bug自己改,你知我知
那我们通过调试,来看这一只滂臭的虫(也可能不止一只)在哪儿。

还原案发现场:

 除虫:

int main()
{
	int i = 0;
	int sum = 0;
	int n = 0;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		int ret = 1;
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}
输入:3
输出:9

当程序执行结果与我们思路不一样的时候,然后我们将其改为与我们思路一致,这就是调试,找bug(1/1)达成!

4.2 实例2

趁热打铁,我们继续第二个例子

代码如下:

int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <= 13; i++)
	{
		printf("又在写bug?\n");
		arr[i] = 0;
	}
}

输出如图:

程序死循环了?有了第一次的经验,我们看一眼代码,发现属于数组越界访问了,但是为什么会死循环?我们还是通过调试,去一探究竟。

蛛丝马迹:

我们发现,i的值可能与arr[12]有关,那可以去它家看看,他们是不是住一起(去查看内存中存储的地址)

好家伙,果然它俩穿同一条裤子,这其实就是,先创建的i,然后再创建数组,地址是由高到低存储,数组越界访问,访问到i的地址了,所以就会出现死循环。

我们在前面提到了Release会对代码进行优化,那我们看看,这个代码,Release会不会优化
运行结果:

我们再来看看,i和arr存储的地址:

编译器是相当的聪明啊,i的地址放到数组的上面去了,那么数组越界就不会访问到i,还得是人工智能啊。

5、如何写出好的代码

刚才讲了那么多如何找bug,如何调试,这些都是作为程序员必修的技能;但我们平时在写代码的时候,就应该防患于未然,写出高质量代码、写出便于观看的代码、写出出问题时便于出找bug的代码。下面,我们来学习学习,如何做一名高质量程序员。

5.1 优秀的代码

1. 代码运行正常(必然要求)

2. bug很少

3. 效率高

4. 可读性高

5. 可维护性高(便于协调操作)

6. 注释清晰(自己或他们可看懂)

7. 文档齐全

Coding技巧

1. 使用assert

2. 尽量使用const(下面会讲)

3. 养成良好的编码风格

4. 添加必要的注释

5. 避免编码的陷阱。

5.2 const的作用

const是个什么大小,我们直接上代码演示,先看看

int main()
{
	int num1 = 10;
	const int num2 = 20;
	num1 = 11;
	num2 = 22;
	return 0;
}

我们发现,num2前面加了了const之后,num2的值修改不了了,报错显示"左值指定const对象"
这就是const的特性,被修饰的值,无法修改,相当于上了一个锁。

但是,上锁的大小,就安全了吗?我们来看看下面这段代码:

int main()
{
	int num1 = 10;
	const int num2 = 20;
	int* p = &num2;
	num1 = 11;
	*p = 22;
	printf("num1=%d num2=%d", num1, num2);
	return 0;
}

前门虽然上了锁,后门没锁,那我走后门可以吧
上有政策,下有对策,那我们就把后面也锁起来,看看下面这段代码:

int main()
{
	int num1 = 10;
	const int num2 = 20;
	const int* p = &num2;
	num1 = 11;
	*p = 22;
	printf("num1=%d num2=%d", num1, num2);
	return 0;
}

这样就安全了?不是,我不找你了,我直接把你的地皮给别人,你去哪我不管,看下面这段代码:

int main()
{
	int num1 = 10;
	const int num2 = 20;
	const int* p = &num2;
	int n = 22;
	p = &n;
	return 0;
}

真.上有政策 下有对策,哈哈哈哈

好,进入正题,我们刚刚发现,const修饰指针变量的时候,放在*的左边右边都可以

const放在*的左边,const修饰的是指针指向的内容,表示指针的内容,不能通过指针改变,但可以通过指针变量来改变

int main()
{
	//const放在*左边
	const int num = 10;
	const int* p = &num;
	int n = 20;
	*p = 100;//no
	p = &n;//yes
	return 0;
}

const放在*的右边,const修饰的指针变量本身,表示指针变量本身不可修改,但指针指向的内容可以通过指针来改变

int main()
{
	//const放在*右边
	const int num = 10;
	int* const p = &num;
	int n = 20;
	*p = 100;//yes
	p = &n;//no
	return 0;
}

6、编程常见的错误 

6.1 编译型错误

此类错误相对简单,一般属于语法错误,双击错误信息

6.2 链接型错误

 此类一般是标识符名不存在或者拼写错误。

解决办法:CTRL+F,搜索报错信息

6.3 运行时错误 

这个是最难搞的,就是能运行,不报错,如何没按自己的逻辑运行,这个就是需要通过调试,逐步排查。

结语:

  关于调试这方面,一定是要在实践中学习的,敢于面对问题,解决问题的过程中也就是自我提升的 过程中,当调试水平上来后,代码水平会有一个质的飞跃,会对代码有更深的理解;
  本期分享就到这里,如果有帮助的话,不妨按个大拇指支持一下;
  好了,下机,再见(要准备期末考试,可能会有一段时间不更)!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值