初学者小白复盘9之——VS实用调试技巧

#VibeCoding·九月创作之星挑战赛#

1.bug是什么?

bug相信大家都是耳熟能详的,从小时候大家打游戏的时候,有些人可能会说,我靠,这游戏有bug啊!那么bug到底是什么意思呢?
其实呢,bug本意为昆虫或虫子,不过现在通常是指某个程序出现了漏洞,或者是隐藏的缺陷问题,简称,程序漏洞。

2.什么是调试?(debug)

调试指的是当我们在写完某一段代码,完成某一个负责的部分后,我们肯定要对程序进行调试,好发现程序中出现的问题,那么接下来就是解决问题并消除问题。这个找问题的过程就叫做调试,英文名成为debug。
那么首先当我们的代码出现了问题时,去调试一个程序,我们要承认我们的代码出现了某个问题,然后通过调试去定位问题,去解决问题,而不是否认问题的出现。

3.Debug和Release

在这里插入图片描述
我们在vs上可以看到Debug和Release,那么他们分别代表着什么意思呢?
Debug被称为调试版本,它包含着调试信息,当我们写代码时,都是在Debug版本下敲写代码的,因为它可以直接进行调试。
Release版本被称为发布版本,简而言之,这是面向用户的版本,在Release版本下,我们的代码将不可以在进行调试,不过Release版本下,会对我们的代码进行一定的优化,使得程序在大小和速度上都是最优的,以便用户使用。当我们写完代码时,测试我们的代码程序达到用户的要求后,便会将Release版本交给用户使用。
如下图例子:
在这里插入图片描述
这是Debug版本的,让我们看看Release版本:
在这里插入图片描述
通过对比可以发现,Release版本下的代码确实会优化,比Debug版本下的大小要小。

4.VS调试快捷键

4.1配置环境

我们需要在Debug环境下测试代码

4.2常用快捷键

F9:创建断点和取消断点
断点的作用是可以在程序的任意位置设置断点,打上断点就可以使得程序执行到想要的位置暂停执行,接下来我们就可以使用F10,F11这些快捷键,观察代码的执行细节。
F5:启动调试,经常用来直接跳到下⼀个断点处,⼀般是和F9配合使用。
F10:逐过程,通常同来处理⼀个过程,⼀个过程可以是⼀次函数调用,或者是⼀条语句。
F11:逐语句,就是每次都执行⼀条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部。在函
数调用的地方,想进入函数观察细节,必须使用F11,如果使用F10,直接完成函数调用。
CTRL+F5:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。

5.监视和内存观察

5.1监视

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	char c = 'w';

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
	}
	return 0;
}

如上代码,如果我们不清楚这个程序是如何运行的,不知道数组里面的值如何去发生变化,那么我们可以通过监视去查看这个程序运行时的变化(开始调试后,在菜单栏中【调试】->【窗口】->【监视】,打开任意⼀个监视窗口,输⼊想要观察的对像),如下:在这里插入图片描述
我们可以发现,当我们的程序还未运行时,里面的值都还未知,当我们按下f11后,我们可以逐语句的去观察程序所发生的变化:
在这里插入图片描述
我们可以观察到,现在我们的初始值都已经初始化好了,接下来,我们可以继续进一步观察变化:
在这里插入图片描述
我们可以发现,随着i的增加,数组arr中的内容也在随之发生变化,由此,我们可以很好的观察到整个程序的运行。

5.2内存

如果认为监视观察的不够仔细,我们还可以去通过内存去观察(跟监视同等操作,在这里不再叙说)
在这里插入图片描述
我们在这里可以观察到内存,右边的列可以换成1行2行等等:
在这里插入图片描述
在这里插入图片描述
右侧为内存中的数据解析,我们可以输入对应的地址去观察程序,
只需要&arr的地址即可,其他的变量也是相同的道理。
在这里插入图片描述

6.调试举例

6.1举例1

求1!+2!+3!+4!+…10! 的和。
我们首先可以想办法求出n的阶乘,再然后求和就好啦!
那么我们可以通过循环去完成这个操作,如下代码:

#include <stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = 1;
	for (int i = 1; i <= n; i++)
	{
		ret *= i;
	}
	printf("%d\n",ret);
	return 0;
}

在这里插入图片描述
可以发现5!确实是120,我们的代码并没有出错,如若出错,我们还可以通过监视与内存观察我们出错的位置,接下来,我们就要完成求和的代码了。

#include <stdio.h>
int main()
{
	int sum = 0;
	int n = 0;
	scanf("%d", &n);
	int ret = 1;
	for (n = 1;n<=3;n++)
	{
		for (int i = 1; i <= n; i++)
		{
			ret *= i;
		}
		sum += ret;
	}
	printf("%d\n",sum);
	return 0;
}

假如我们求3!,如上,当n==1,2,3时,代表着求1!2!3!,再用sum求和,看似很完美,实则当我们运行起来时会发现:在这里插入图片描述
不对啊?求和加一起为1+2+6不应该等于9吗,怎么会是15呢?那我们哪里出现了问题呢?我们来一起监视一下!在这里插入图片描述
可以发现,当n等于1时,没问题,接下来继续看:在这里插入图片描述
n等于2时,sum等于3,也没问题,继续看:在这里插入图片描述
在这里插入图片描述
我们现在会发现,哎呀,这不对啊,这sum怎么就变成15了呢?我们再仔细一瞅,我靠,这ret怎么干成12了,这啥情况,继续往前看,ret变成了4,再继续看ret变成了2,到这里,真相已经大白了,这一切都是ret搞的鬼!
我们想要算阶乘,ret必须复原为1,但是我们发现ret在算阶乘时并没有复原为1,这就是问题的关键,所以我们做出如下改变,就能正确算出答案:
在这里插入图片描述
如下图:

#include <stdio.h>
int main()
{
	int sum = 0;
	int n = 0;
	scanf("%d", &n);
	for (n = 1;n<=10;n++)
	{
		int ret = 1;
		for (int i = 1; i <= n; i++)
		{
			ret *= i;
		}
		sum += ret;
	}
	printf("%d\n",sum);
	return 0;
}

6.2举例2

在VS2022、X86、Debug的环境下,编译器不做任何优化的话,下⾯代码执行的结果是啥?

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

答案是,程序会死循环,那么为什么程序会死循环呢?我们可以通过监视来观察一下。
在这里插入图片描述
接下来进入for循环,进一步观察:
在这里插入图片描述
数组arr在慢慢每个元素变为0,相对应的在屏幕上打印hehe,我们看看当i超过11时会发生什么?在这里插入图片描述
可以发现,arr【10】和arr【11】居然也都变为0了,那么我们来看看arr【12】会不会变成0呢?在这里插入图片描述
可以发现,arr【12】居然和i一起变为了0?那我们来取一下它两的地址看看。在这里插入图片描述
我们可以发现,arr【12】居然和i是同一个地址

在这里插入图片描述
栈区内存的使用习惯是从高地址向低地址使用的,所以变量i的地址是较大的,arr数组的地址整体是小于i的地址。 数组在内存中的存放是:随着下标
的增长,地址是由低到高变化的。
如果是左边的内存布局,那随着数组下标的增长,往后越界就有可能覆盖到i,这样就可能造成死循环的。
这⾥肯定有同学有疑问:为什么i和arr数组之间恰好空出来2个整型的空间
呢?这里确实是巧合,在不同的编译器下可能中间的空出的空间大小是不一样的,代码中这些变量内存的分配和地址分配是编译器指定的,所以的不同的编译器之间就有差异了。所以这个题目是和环境相关的。
注意:栈区的默认的使用习惯是先使用高地址,再使用低地址的空间,但是这个具体还是要编译器的实现。
比如:
在VS上切换到X64,这个使用的顺序就是相反的,在Release版本的程序中,这个使用的顺序也是相反的

感谢大家的收看,如果认为这篇文章对你有所帮助,可以给一个点赞哦~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值