前言
大家好,今天给大家带来的是VS的实用调试技巧
这是我们学习编程一定要学会的
如果大家看过我的上一篇关于扫雷的博客(在最底下的专栏目录中),并且大家有自己去写过扫雷,可能在写的过程中会遇到各种挫折,为了解决程序的问题,我们就会用到今天的关于调试的知识
好了,废话不多说,开始我们今天的一个分享吧
1.什么是bug
bug本意的是虫子,但现在一般是指在电脑系统或程序中,隐藏着的⼀些未被发现的缺陷或问题,简称程序漏洞
关于bug这个词语的由来,大家感兴趣的话,可以点击下面的这个链接去自行了解哦
2.什么是调试
不知道大家在平时写程序时有没有出现过以下的这个情况
当我们发现程序出了问题后,可能有很多同学(包括现在的我)都在循环上面的这张图
其实这就引出了调试的概念:
调试描述的是一个找问题的过程,英文叫debug
上面的图片其实就是一种调试,但这是一种不科学的方式,想要知道bug到底是怎么写出来的,为此,我们就要学会科学的调试方法
下面是调试的一般过程:
1.首先是承认出现了问题
2.然后通过各种手段去定位问题的位置,可能是逐过程的调试,也可能是隔离和屏蔽代码的方式,找到问题所在的位置
3.然后确定错误产生的原因
4.再修复代码, 重新测试
说到这,可能大家已经按耐不住想去写代码了,但大家先别急
在开始正式的调试之前,我们再来了解一个概念:Debug 和 Release
3.Debug和release
那什么是Debug和Release呢?
打开VS,我们在界面上会看到下面图片上圈起来的
这就是 Debug 和 Release
Debug通常称为调试版本,程序可以调试,并且不作任何优化,便于程序员开发
Release为发布版本,这是给用户的版本,此时的版本已经做了各种优化,但程序不能调试
所以我们在开始调试之前,一定要先设置成Debug模式才可以
如果是Release则没有调试功能!
4.VS调试快捷键
下面我们来介绍几个快捷键,方便各位进行调试的操作
1.F9 : 创建断点和取消点
2.F5
3.F10
4.F11
5.CTRL+F5:这个快捷键我们很熟,我们至今写过了所有代码运行都按这个快捷键
它的意思是 开始执行不调试,如果我们想让程序直接运行而不调试就可以使用
(现在你知道为什么按CTRL+F5了吧)
首先我们来看F9 什么是断点?
这个红色的点就是断点
其作用是可以在程序的任意位置设置断点,打上断点就可以使得程序执行到想要的位置暂定执行,接下来我们就可以使用F10,F11这些快捷键,观察代码的执行细节
然后我们看一下F5,它一般是和F9配合使用,作用是启动调试,经常用来直接跳到下一个断点处
大家看,我们启动调试后按下F5,它会从第一行直接跳到断点所在位置,跳过前面所有的代码
这可以让我们随时调试我们想调试的部分,非常的便捷
最后我们再来了解两个快捷键:F10和F11
F10:逐过程,通常来处理一个过程,一个过程可以是一次函数调用,或者是一条语句
F11:逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部
在函数调用的地方,想进入函数观察细节,必须使用F11,如果使用F10,直接完成函数调用
可以看出,虽然两者都是逐过程的调试,但F11的调试力度要更细
关于断点,我们再来讲一个东西:条件断点
请看下面这张图
当我们将条件设置为i==8是,再次调试,我们看一下监视窗口
我们只调试了一次,因为条件断点的原因,i直接跳过了01234567,来到了8,因为我们条件设置的就是i==8,这就是条件断点很巧妙的一个作用
5.监视和内存观察
在调试的过程中我们,如果要观察代码执行过程中,上下文环境中的变量的值,有哪些方法呢?
那我们就来说说监视和内存观察
1.监视观察
关于监视观察,我们在上面讲条件断点的时候就用到了
打开监视窗口的步骤如下:
启动调试
然后打开监视窗口(4个窗口随便点开一个就行,这里用监视1举例)
添加要监视的项并观察
2.内存观察
如果监视窗口看的不够仔细,也是可以观察变量在内存中的存储情况
步骤如下
我们可以看到这里可以输入一个地址
还是那上面讲监视观察的代码举例,如果我们想看arr的地址,那我们就在这个地址:输入arr
让我们看一下结果
这个时候地址会自动转换成arr的地址
我们再来试一下num(对于变量,我们输入是要在前面加上&)
注意:关于地址中的内容是什么意思涉及进制转换的知识,这些内容会在以后的博客中分享,大家可以订阅博客最底下的专栏持续的关注我哦
除此之外,在调试的窗口中还有:自动窗口,局部变量,反汇编、寄存器等窗口,大家下去可以自己用一下,玩一下。这里不再过多赘述了
6.调试举例
1.例子1
请看下面的代码,运行的结果是什么呢?
在x86环境下(不是x64),打印的结果是死循环的 hehe
我们可能只发现数组越界,但结果的死循环是不是意料之外呢?
所以这时候如果不调试你压根就不知道为什么
我们来打开监视窗口看一看吧
我们发现,当i大于10时,数组不仅“明目张胆”的越界,而且此后还将arr[ 10 ] 擅自赋值为0
当i = 11 后也将arr[ 11 ]赋为0
而当我们将arr[12]也赋为0后,我们发现i也变为了0!
其实我们细心一点,就会发现此前的arr[12]就一直与i保持同步
我们再将它们俩的地址取出了看一下
我们发现它们的地址是一致的 这样也就解释得通死循环的原因了
不过为什么又会出现这种情况呢?是偶然还是必然?
下面我们再来分析一下:
在我们上面写的这个代码中,i和arr都是局部变量
我们知道,局部变量在内存中是存在栈区的
栈区内存的使用习惯是从高地址向低地址使用的,而数组在内存中的存放是:随着下标的增长,地址是由低到高变化的
如图
所以上述代码中的变量i的地址是较大(高)的。(因为我们先定义了i再定义arr)arr数组的地址整体是小(低)于i的地址
所以根据这些,就能理解为什么了
如果是上面的内存布局,那随着数组下标的增长,往后越界就有可能覆盖到i,这样就可能造成死循环的(那上图来看就是i和arr 数组之间恰好空出来2个整型的空间)
这里肯定有同学有疑问:
为什么i和arr 数组之间恰好空出来2个整型的空间 呢?
注意:这里确实是一个巧合事件,可能在不同的编译器下中间的空出的空间大小是不一样的, 所以 这个题目是和环境相关的(这里的环境就是VS2022 Debug模式 x86)
虽然是巧合,但通过这个例子我们也确实感受到了调试有多么重要了!
2.例子2
上面的代码其实代码量是比较小的
但如果代码稍微复杂一点呢?
我们又该怎么调试呢?
这里我们就上手调试一下我们上一篇博客写的扫雷的代码
假设我在写扫雷的代码时遇到了下面这种情况:
在我的扫雷中,初始化棋盘没问题,打印棋盘也没问题,但我发现布置雷有问题
这个时候怎么办呢?
首先我们在SetMine函数内部打断点,快速跳转到函数SetMine
然后我们打开监视窗口进行调试观察
调试是需要反复去动手练习的
要多敲代码,千万不要因为觉得太简单而不敲,敲与不敲是两回事,要有空杯心态!
7.编程常见错误归纳
好了,重头的关于调试举例的分析说完了,接下来的内容就很轻松了
其实下面的第7点是一个拓展包
我们在这里对编程常见的错误进行一个归纳,我们写代码出现的错误基本上都可以归为以下三类
1.编译型错误
编译型错误⼀般都是语法错误,这类错误一般看错误信息就能知道为什么了,
编译型错误,随着对语言的熟练掌握,会越来越少,也容易解决
2.链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在
一般是因为 :
1.标识符名不存在
2.拼写错误 (比如字母大小写没对上)
3.头头件没包含或者引用的库不存在
3.运行时错误
运行时错误,一般是我们想要的结果与程序运行的结果不同
这种错误的形式是千变万化的,也正是需要借助我们今天讲的调试来逐步定位问题,
结语
好了,今天的分享就到这里了
最后,希望大家点个赞或者关注吧(感谢感谢)
让我们在接下来的时间里一起成长,一起进步吧!