VS实用的调试技巧

目录

前言

一、是什么bug?

二、什么是调试(Debug)

三、Debug和Release

四、VS调试快捷键

4.1 环境准备

4.2 调试快捷键 

五、监视和内存观察

5.1 监视 

 5.2 内存

六、举例

6.1 例一

 6.2 例二

七、数组传参小技巧

八、编程常见错误归类

1. 编译型错误

2. 链接型错误 

 3. 运行时错误

总结


前言

前面的内容我们已经讲到了函数,但是,很多人对函数调试不是很熟悉,这期我们来讲一下VS函数调试技巧


一、是什么bug?

bug本意是“昆⾍”或“⾍⼦”,现在⼀般是指在电脑系统或程序中,隐藏着的⼀些未被发现的缺陷或问题,简称程序漏洞。
“Bug” 的创始⼈格蕾丝·赫柏(Grace Murray Hopper),她是⼀位为美国海军⼯作的电脑专家,1947年9⽉9⽇,格蕾丝·赫柏对Harvard MarkII设置好17000个继电器进⾏编程后,技术⼈员正在进⾏整机运⾏时,它突然停止了工作。于是他们爬上去找原因,发现这台巨⼤的计算机内部⼀组继电器的触点之间有⼀只⻜蛾,这显然是由于⻜蛾受光和热的吸引,⻜到了触点上,然后被⾼电压击死。所以在报告中,赫柏⽤胶条贴上⻜蛾,并把“bug”来表⽰“⼀个在电脑程序⾥的错误”,“Bug”这个说法⼀直沿⽤到今天。

历史上的第⼀个bug(图⽚来⾃⽹络) 

二、什么是调试(Debug)

当我们发现程序中存在的问题的时候,那下⼀步就是找到问题,并修复问题。
这个找问题的过程叫称为调试,英⽂叫debug(消灭bug)的意思。调试⼀个程序,⾸先是承认出现了问题,然后通过各种⼿段去定位问题的位置,可能是逐过程的调试,也可能是隔离和屏蔽代码的⽅式,找到问题所的位置,然后确定错误产⽣的原因,再修复代码,重新测试。

三、Debug和Release

如图:在VS上编写代码的时候,就能看到有 debug 和 release 两个选项,分别是什么意思呢? 

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序;
程序员在写代码的时候,需要经常性的调试代码,就将这⾥设置为 debug ,这样编译产⽣的是debug 版本的可执⾏程序,其中包含调试信息,是可以直接调试的。

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码⼤⼩和运⾏速度上都是最优的,以便用户很好地使⽤。当程序员写完代码,测试再对程序进⾏测试,直到程序的质量符合交付给⽤⼾使⽤的标准,这个时候就会设置为 release ,编译产⽣的就是 release 版本的可执⾏程序,这个版本是⽤⼾使⽤的,⽆需包含调试信息等。 

 我们还可以打开文件看看:

Debug:

Release: 我们可以看到,Release版本在代码大小下遥遥领先;

四、VS调试快捷键

4.1 环境准备

⾸先是环境的准备,需要⼀个⽀持调试的开发环境,我们上课使⽤VS,应该把VS上设置为Debug,如图:

4.2 调试快捷键 

调试最常使⽤的⼏个快捷键:

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

光看没用,来点例子(我们以以下代码为例):

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("hehe\n");
	}

	for (i = 0; i < 10; i++)
	{
		printf("wuwu\n");
	}
	return 0;
}

一般断点不会直接使用,它会配合其他的快捷键使用:

假设我们认为是第25行可能出问题了,但是我们不可能一直调试到25行,所以我们先在 25行设置断点,然后再按F5,就可直接调至25行:

但是要注意,F5是调至程序执行逻辑的断点处,我们接着上面的例子:

我们现在又在Return 0;添加断点,如果我们再按F5,会跳到哪?

 我们来看结果:

这说明了,我们在按下F5的时候还是调至25行:这告诉我们:

 F5是调至程序执行逻辑的断点处

所以这里我看到很多人运行程序嫌麻烦直接按F5,但是其实,F5的意思是调试,只是你没有设置断点,所以将程序执行完了,在2013的Vs中,按F5程序会一闪而过,并不会显示执行结果;

F10和F11看上面的介绍可能会很懵,没关系,我们来看看以下的例子看你能不能理解:

 我们在上面的例子中加入一个函数:

void test()
{
	printf("kaka\n");
}

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("hehe\n");
	}

	for (i = 0; i < 10; i++)
	{
		printf("wuwu\n");
		test();
	}
	return 0;
}

 我们还是在原来的地方设置断点并按F5:

但这次不同的是我们分别按F10与F11大家看看有何区别:

按F10,可以看到他直接执行了函数,并没有进入函数内部:

按F11,你会发现会进入函数内部;

 到这里了,你应该了解了F10与F11的区别。

还有其他快捷键:VS中常用的快捷键_vs快捷键-CSDN博客

五、监视和内存观察

在调试的过程中我们,如果要观察代码执⾏过程中,上下⽂环境中的变量的值,有哪些⽅法呢?
这些观察的前提条件⼀定是开始调试后观察,⽐如:

int main()
{
 int arr[10] = { 0 };
 int num = 100;
 char c = 'w';
 int i = 0;
 for (i = 0; i < 10; i++)
 {
 arr[i] = i;
 }
 return 0;
}

5.1 监视 

开始调试后,在菜单栏中【调试】->【窗⼝】->【监视】,打开任意⼀个监视窗⼝,输⼊想要观察的对象就⾏。
打开监视窗⼝:

在监视窗⼝中观察:

 5.2 内存

如果监视窗⼝看的不够仔细,也是可以观察变量在内存中的存储情况,还是在【调试】->【窗⼝】->【内存】

打开内存窗⼝:

 在打开内存窗⼝后,要在地址栏输⼊:arr,&num,&c,这类地址,就能观察到该地址处的数据。

这时候我们如果想要显示数组的地址,我们输入&arr吗?数组的名字就是代表数组的地址:

 我们知道这个数组是int类型的:

我们为了看的清楚。我们可以将列显示为4行:

现在我们就可以清晰的看到数组里面的各个元素值; 

除此之外,在调试的窗⼝中还有:⾃动窗⼝,局部变量,反汇编、寄存器等窗⼝;

六、举例

6.1 例一

求 1!+ 2!+ 3!+ 4!+ ...10!的和

//求 1!+ 2!+ 3!+ 4!+ ...10!的和,请看下⾯的代码:

int main()
{
	int n = 0;
	int i = 1;
	int ret = 1;
	int sum = 0;
	for (n = 1; n <= 3; n++)
	{
		for (i = 1; i <= n; i++)
		{
			ret *= i;
       	 }
		sum += ret;

	}

	printf("%d\n", sum);
	return 0;
}

运行结果:

我们来调试一下:

我们发现,前面一切正常,但是当n=3时, 此时在执行求阶乘的代码的时候,ret并不是1,而是2,这就产生了叠加效果;我们可以这样改:

int main()
{
	int n = 0;
	int i = 1;
	int sum = 0;
	for (n = 1; n <= 3; n++)
	{
		int ret = 1;
		for (i = 1; i <= n; i++)
		{
			ret *= i;
       	 }
		sum += ret;

	}

	printf("%d\n", sum);
	return 0;
}

现在的运行结果就是对的了:

但这并不是一个好代码,我们可以这样写:

int main()
{
	int n = 1;
	int ret = 1;
	int sum = 0;
	for (n = 1; n <= 3; n++)
	{
		ret *= n;
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

 6.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;
}

我们来看看结果:

 死循环了?为什么,你可以直接看出来吗?

我们来调试看看:

当循环执行10次的时候:

 我们发现,打印了10次,符合我们预想,但是下次循环,i变成10,arr[10]?这是什么?不用着急,我们来一探究竟:

他是一个不知道什么意义的数?我们可以这么认为,他越界了,但程序可以修改吗?我们继续看看:

 程序照样修改:

我们接着往下看:

到我们执行12次的时候,我们发现,i与arr[12]一同发生了变化,这样,程序才进入了死循环,但,为什么?

我们来看看他们两个的地址:

一模一样,这就是为什么了! 

至于为什么是这样的,我们接着往下看:

调试可以上⾯程序的内存布局如下:

1. 栈区内存的使⽤习惯是从⾼地址向低地址使⽤的,所以变量i的地址是较⼤的。arr数组的地址整体是⼩于i的地址。

2. 数组在内存中的存放是:随着下标的增⻓,地址是由低到⾼变化的。所以根据代码,就能理解为什么是左边的代码布局了。

如果是左边的内存布局,那随着数组下标的增⻓,往后越界就有可能覆盖到i,这样就可能造成死循环的。

这⾥肯定有同学有疑问:为什么i和arr数组之间恰好空出来2个整型的空间呢?这⾥确实是巧合,在不同的编译器下可能中间的空出的空间⼤⼩是不⼀样的,代码中这些变量内存的分配
和地址分配是编译器指定的,所以的不同的编译器之间就有差异了。所以这个题⽬是和环境相关的。vc 6.0中间不空;

 从这个理解我们能够体会到调试的重要性,只有调试才能观察到程序内部执⾏的细节,就像医⽣给病⼈做B超,CT⼀样。

注意:栈区的默认的使⽤习惯是先使⽤⾼地址,再使⽤低地址的空间,但是这个具体还是要编译器的实现,⽐如:

在VS上切换到X64,这个使⽤的顺序就是相反的,在Release版本的程序中,这个使⽤的顺序也是相反的。

七、数组传参小技巧

在数组传参,调试进⼊函数,如何在监视窗⼝观察数组的内容: 数组名,n 的形式

二维数组也是一样的:

调试过程中,要做到⼼中有数,也就是程序员⾃⼰⼼⾥要清晰的知道希望代码怎么执⾏,然后再去看代码有没有按照我们预定的路线在执⾏。

调试是需要反复去动⼿练习的,调试是可以增加程序员对代码的理解和掌控的,掌握了调试的能⼒,就能看到本质,就像能给程序做B超⼀样,对程序内部⼀览⽆余。

八、编程常见错误归类

1. 编译型错误

编译型错误⼀般都是语法错误,这类错误⼀般看错误信息就能找到⼀些蛛丝⻢迹的,双击错误信息也能初步的跳转到代码错误的地⽅或者附近。编译错误,随着语⾔的熟练掌握,会越来越少,也容易解决.

2. 链接型错误 

• 标识符名不存在
• 拼写错误
• 头⽂件没包含
• 引⽤的库不存在

 3. 运行时错误

运⾏时错误,是千变万化的,需要借助调试,逐步定位问题,调试解决的是运⾏时问题。


总结

这期我们对函数的调试进行了总结,以后我们在学习C语言的同时,我们要学会发现问题,并自己找到问题,再解决,新学期,26考研加油!

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值