C语言 VS 调试技巧篇
目录1.引言2.Bug 与 Debug 的概念3.VS 的 Debug 模式与 Release 模式4.VS 的调试快捷键5.监视窗口与内存窗口6.调试实操7.错误类型8.结语1.引言我们在上篇博客尝试制作了扫雷游戏不过由于博主讲解与实操的疏忽做出来的扫雷游戏可能仍有问题那也欢迎各位小萌新在评论区留言指正。然而不光是扫雷游戏在之后的学习中我们对自己所写的代码进行功能测试时也可能发现代码存在或多或少的问题。有些问题我们能一下找到错误出处而有些问题可能隐藏较深不易发现。此时就需要用到本篇博客所讲解的VS调试技巧我们可以通过调试的方式测试程序来发现隐藏漏洞。2.Bug 与 Debug 的概念bug 本意是指虫子现在一般指电脑程序或系统中隐藏着未被发现的缺陷或问题也就是程序漏洞。针对于这些影响程序运行的漏洞我们肯定要找到并修复而找漏洞的这个过程即为调试也就是 debug。找漏洞的方式有很多我们可以通过逐行调试的方式来定位问题所在位置也可以通过隔离与屏蔽代码的方式来定位。调试往往不是一次性的过程我们在找到问题之后首先是确定问题产生的原因其次才是修复而且之后还要重新测试如果发现新问题就要继续确定错误原因修改与测试。3.VS 的 Debug 模式与 Release 模式我们在 VS 上面的菜单栏里是可以选择程序的运行模式的VS 有 Debug 模式与 Release 模式而上篇博客的结尾我们也是以Release模式来运行分享我们的扫雷游戏。那这两个模式有什么区别Debug 模式为调试模式在该模式下包含调试信息我们可以直接对所写代码进行调试但不做任何代码优化是适用于程序员调试代码的系统模式而 Release 模式为发布模式在该模式下进行了各种优化程序的代码大小与运行速度快慢都是最优的但无法进行调试操作是适用于用户使用程序的系统模式。下面附上图片我们可以通过同一个代码在两个模式下的不同表现直观感受区别。先是在Debug 模式下在对应 Debug 文件夹下 .exe文件大小为51kb。不知道怎么打开文件夹的看上篇博客结尾不再重复讲解了。代码可以逐行调试。现在换成 Release 模式在对应 Release 文件夹下 .exe文件大小为10kb。代码无法逐行调试。4.VS 的调试快捷键1.CtrlF5开始执行不调试当我们想要直接运行程序而不是调试程序时可以使用2.F10逐过程调试通常用来处理一个过程一次过程可以是一条语句也可以是一次函数调用。3.F11逐语句调试每次都只执行一条语句利用F11我们可以进入函数内部调试在使用函数调用的地方若想观察函数细节必须使用F11进入函数内部若使用F10则直接完成函数调用。4.F9创建断点与取消断点断点的作用就是当程序执行到断点位置就会停止执行利用断点配合F10或F11我们可以更好地观察代码的执行细节。断点也是有属性的我们可以通过设置来创建条件断点条件断点可以帮助我们精确判断代码的异常情况。5.F5启动调试常用来快速跳到下一个断点位置需要与F9配合使用。如下图最开始按F10进入调试此时指向第2行当我们按F9在第8行打上断点后再按F5就会直接略过前面的代码来到第8行所在位置。利用这一点当我们知道代码出问题的大致位置时可以加快排查速度。快捷键可以让我们在使用VS时更为方便那有关VS的其他快捷键了解可以去看这篇博客https://blog.csdn.net/mrlisky/article/details/72622009。5.监视窗口与内存窗口我们在前面的学习中已经简单介绍过监视窗口的使用方法而与监视窗口类似的是内存窗口。内存窗口使用方法与监视窗口一样我们按F10进入调试状态后在上面的菜单栏里找到调试选择窗口右侧根据需要选择对应的窗口常用的是监视窗口与内存窗口。监视窗口我们知道他可以帮助我们观察变量在程序运行过程中的变化情况那内存窗口有什么用利用内存窗口我们可以检查一个变量所在的地址以及该地址所存放的值通过地址的变化与地址内存放的值的变化我们可以知道在函数调用时是否成功将值传入是否成功将值返回以及对于数组这些拥有连续地址的内部元素的存放情况等信息。更多内容会在下篇博客讲解我们现在只要知道内存窗口也可以帮助我们观察变量的变化情况即可。这里在内存窗口的地址后输入 arri 这些代表地址的表达式就可以看到对应变量的地址信息通过与右侧监视窗口的对比内存窗口里的地址信息与地址内存放的值的信息都是准确的我们可以借助内存窗口对代码观察的更仔细。同时我们可以根据所创建数组的数据类型调整内存窗口展示的数据长度默认是自动也就是根据窗口大小自动调整展示长度。虽然计算机是以二进制储存数据的但为了展示方便地址以及地址内数据都是以十六进制显示的。6.调试实操我们之前学过循环语句也学过函数递归如果现在还要求写一个代码求一个数的阶乘相信各位小萌新肯定能写出来了代码大致如下。我们测试运行一下后也证明代码没有问题。那如果是将代码改为求从1到n所有数的阶乘之和我们也只要再创建一个变量用于存放求和的结果即可代码大致如下。我们同样测试运行一下但是结果好像不对那到底是哪里写错了此时我们就可以用调试的方式来查找代码错误并将其改正。正确的调试过程是先按F10进入调试然后调出我们的监视窗口然后在监视窗口中输入我们要监视的变量然后根据实际情况逐过程或逐语句或打断点等进行调试然后通过右侧监视窗口的数据变化判断代码错误的位置然后思考错误原因然后修正并再次测试。通过上述过程我们成功发现了代码的错误所在也发现了造成错误的原因接下来就是修改代码了。实际上我们只要将 num 的位置从循环外移到循环里就能解决代码错误。至于为什么我们之前也是讲过的原本 num 的作用域大可以影响整个大循环内循环改变 num 的值就会对之后的循环结果都产生影响而修改后 num 的作用域变小只能影响下面这个内循环每次内循环结束后再次进入num 都会被重置为1我们可以通过调试来检验。那除了修改 num 的位置我们还能修改代码的运行逻辑。num 变量存放了每个数的阶乘结果而 sum 变量存放了每一次的求和结果。但每个数的阶乘相比于前一个数只是多了自己那部分那num 只要在之前的基础上再乘上 i就能得到新数字的阶乘结果而 sum 也是同样的逻辑每次都只是在之前的基础上加上新数字的阶乘结果。那么代码也可以写成下面这样。经过测试运行我们发现这样也能解决代码原有的错误问题。所以写代码的方式也很重要。在清楚了调试过程以及上一个程序的错误原因之后我们再来看一个例子代码如下图。这个代码要跑出错误是有环境要求的大家在VS的 Debug 模式x86 环境下去运行就会发生死循环。至于为什么会这样我们同样可以通过调试的方法不过这里我们还要用到内存窗口。当我们按F10进入调试之后一开始可以正常调试完前面10个此时已经将 arr 数组元素的值全部修改为 0。再次按F10就会对 arr[10] 进行修改可 arr 数组的下标到 9 就结束了那这里就发生了越界访问将不属于 arr 数组的地址的对应数值也修改了。此时我们引入变量 i继续按F10调试那么随着 i 的值继续增加对应的 arr[11] 也继续被修改为 0。但当我们继续按F10就会发现 arr[12] 的值不同于 arr[10] 与 arr[11]他的值与 i 相同然后再搜索一下 i 的地址与 arr[12] 的地址就会发现这两其实是同一个地址。那么此时再按F10在修改 arr[12] 的同时等同于修改了 i 的值i 变回 0 之后整个循环又从头开始程序就陷入死循环了所以平时在使用数组时一定要注意越界访问的问题。那同样的代码我们改到 x64 环境下运行就会正常打印 13 次结束之后报堆栈缓冲区溢出的错误。针对于这个例子为什么环境不同结果不同是因为 x86 环境与 x64 环境对于栈区的使用方法不一样。x86 环境下栈区内存从高地址向低地址使用大概情况如下图这样而 x64 环境下刚好反过来栈区内存从低地址向高地址使用。如果将模式改为 Release 模式那无论是在 x86 环境下还是在 x64 环境下栈区内存都是从低地址向高地址使用。至于 i 的地址与 arr 的地址之间到底间隔了多少空间这个取决于编译器。所以一开始时我们也说明了这个例子只有在特定情况下才会生效。通过上面两张图的比较我们清楚了为什么不同环境下一个会死循环一个会报错也明白了x86 的内存开辟情况与 x64 的内存开辟情况。我们平时在使用变量时要注意变量前后创建的顺序在使用数组时要注意数组的临界下标位置防止发生此类错误。7.错误类型通过上面的实操我们已经掌握了调试的整个过程也通过调试发现了代码的错误。那我们在最开始讲解C语言时说过一个C语言程序需要通过编译链接才能运行所以根据程序经历的三个过程我们可以把遇到的代码错误大致分为编译型错误链接型错误与运行时错误这三类。当我们知道代码出错时是出现了什么类型的错误就能更高效的修正错误而我们所用的调试也主要是为了解决运行时错误。1.编译型错误编译错误一般都是语法错误此类错误根据错误列表中的错误信息提示就能马上定位错误位置并迅速改正。编译错误是最容易解决的随着我们的代码能力提高也会越来越少。错误列表如果不小心关闭可以在上方菜单栏的视图里打开。2.链接型错误链接型错误同样会在错误列表中给出错误信息提示解决此类错误我们可以通过定位错误信息中的标识符来找到问题出错的原因所在通常都是因为标识符不存在标识符拼写错误未包含对应头文件引用的库不存在这些原因。若是像之前写扫雷游戏那样有很多自定义函数我们在使用时难免会有拼写错误此时如果想要快速定位函数所在位置我们可以按CtrlF来调出搜索窗口。3.运行时错误运行时错误是最常见的也是最难发现的在我们最开始写代码时可能会出现很多运行时错误而且凭我们的经验很难一下推测出错误原因这时就需要我们通过调试的方式去找到错误并将其修正所以学会调试对我们很重要。那和编译型错误一样随着我们的代码能力提高我们所写的代码会越来越规范在书写时也会主动去注意一些细节问题那运行时错误也会慢慢减少我们排查错误的能力也会慢慢提高。8.结语本篇博客到这里就结束了希望能够帮到各位刚接触C语言的小萌新们下篇博客将继续讲解C语言的相关知识。