当前位置: 首页 > news >正文

VS实用调试技巧

文章目录

  • 一、什么是bug?
  • 二、什么是调试(debug)?
  • 三、debug和release
    • 1.Debug
    • 2.Release
  • 四、VS调试快捷键?
  • 五、监视和内存观察
    • 1.监视:
    • 2.内存
  • 六、调试举例1
  • 七、调试举例2

一、什么是bug?

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

二、什么是调试(debug)?

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

三、debug和release

在这里插入图片描述
    在VS上编写代码的时候,就能看到有 debug 和 release 两个选项,分别是什么意思呢?

1.Debug

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

2.Release

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

两种不同版本的文件大小对比如下:
Debug:
在这里插入图片描述
Release:
在这里插入图片描述
对⽐可以看到从同⼀段代码,编译⽣成的可执⾏⽂件的⼤⼩,release版本明显要⼩,⽽debug版本明显⼤

四、VS调试快捷键?

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

五、监视和内存观察

以以下代码为例:
在这里插入图片描述
此代码没有做任何的输出操作,我们如果想要观察这些变量究竟发生变化没有,或者看看它们在内存中如何存储,就需要使用监视或者内存功能

1.监视:

    首先要先进入调试模式,按下快捷键f10启用调试,或者按【调试】->【开始调试】按钮启用调试,随后按【调试】->【窗⼝】->【监视】,然后任意找一个窗口点开,如下:
在这里插入图片描述
    点开后将打开的窗口放在右边,如图:
在这里插入图片描述
    随后我们将要查看的变量输入进去,如:
在这里插入图片描述
    这里数组arr只显示一个内容,点右边三角形展开,而后显示无法读取内存,就是还没有调试到那一步,按f10继续走下去,走到for循环前再来观察:
在这里插入图片描述
    可以看到前面的内容已经初始化完成了,接下来的for循环就是给数组赋值,可以自行按f10调试,这里我们直接来看看for循环执行的结果:
在这里插入图片描述
    可以看到,for循环成功为我们完成了任务,如果这个代码直接运行什么都不会出现,但是经过我们的调试,我们可以清楚的看到我们写的代码确实起了效果

2.内存

如果监视窗⼝看的不够仔细,也是可以观察变量在内存中的存储情况,首先还是要进入调试状态,然后菜单中【调试】->【窗⼝】->【内存】打开内存窗⼝,如图:
在这里插入图片描述

现在我们把内存窗口打开试试
在这里插入图片描述
内存中这三大类分别是什么呢?如图:
在这里插入图片描述
我们怎样通过内存观察数据呢?就要用到我们的取地址符&,比如想要查看字符变量c,就在内存框中输入&c,如图:
在这里插入图片描述

六、调试举例1

要求 1!+2!+3!+4!+…10! 的和:

  1. 看到需求后发现需要求阶乘,并且将1到10的阶乘相加,所以第一步要求一个数的阶乘,假如有一个数n,它的阶乘就是从1按顺序乘到n,这里我们可以使用for循环,按顺序一个一个乘起来,代码如下:
#include <stdio.h>
//写⼀个代码求n的阶乘
int main()
{int n = 0;scanf("%d", &n);int i = 1;int ret = 1;for (i = 1; i <= n; i++){ret *= i;}printf("%d\n", ret);return 0;
}
  1. 我们知道n的阶乘怎么求了,接下来我们再用一个for循环将它们相加,如下:
#include <stdio.h> 
int main()
{int i = 1;int j = 1;int ret = 1;int sum = 0;for (i = 1; i <= 10; i++)//求1到10的阶乘的和{for (j = 1; j <= i; j++)//求阶乘{ret *= j;//ret是阶乘的值}sum += ret;//sum是各个阶乘相加的结果}printf("%d\n", sum);return 0;
}

    是不是我们的代码完成了呢?运行结果是一个很大的数,我们也不知道对不对,这个时候我们将10改成3试试,运行结果如下:
在这里插入图片描述
    我们简单计算一下,1的阶乘是1,2的阶乘是2,3的阶乘是6,相加是9,与代码算出来的15不一样,错在哪里呢?我们现在肉眼看不出来,就要启用我们的调试功能,按f10一步一步的调试,i=1时循环调试结果为:
在这里插入图片描述
    可以发现现在一切正常,帮我们算出来了1的阶乘,并且加到了sum内部,j=2跳出了第一次for循环,接下来是第二次循环调试结果:
在这里插入图片描述
    我们发现居然还是一切正常,代码帮我们算出来了2的阶乘,sum变成了3,j变成了3跳出循环,到这里一切正常,我们猜到多半下一次循环有问题,我们来看看下一次循环调试的结果:
在这里插入图片描述
    果然,这里出现了问题,ret也就是3的阶乘变成了12,导致我们的阶乘和变成了15,这是为什么呢?我们可以反推一下,我们原本需要得到6,现在变成了12,说明多乘了一个2,这个2怎么来的呢?仔细一想我们就会发现,这个2是之前算出来的2的阶乘,出现这种多乘的结果就是因为每执行一次ret,ret的值就会被改变,由于1的阶乘就是1,ret还是1,所以不会影响下一次求2的阶乘,但是由于2的阶乘是2,ret变成了2,就影响了求3的阶乘,所以问题就出在ret身上,我们需要确保每一次求一个数的阶乘,ret都是1,所以解决办法就是在每一次循环开始前,就将ret重置为1,改正代码为:

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

注意:经过这次调试举例,我们可以看出,有一些情况下我们下意识认为我们的逻辑是正确的,被蒙在鼓里,找不到问题,这时我们就要一步一步调试查找错误,调试对我们的帮助非常大

七、调试举例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 %d\n",i);}return 0;
}

    一运行,我们发现代码居然死循环了,这是为什么呢?
我们来看看以上代码在内存中的存储:
在这里插入图片描述
我们可以看出三点:

  1. 栈区内存的使⽤习惯是从⾼地址向低地址使⽤的,所以变量i的地址是较⼤的。arr数组的地址整体是⼩于i的地址
  2. 数组在内存中的存放是:随着下标的增⻓,地址是由低到⾼变化的
  3. 数组存在越界访问的情况,VS可能会报警告,但是还是会按代码执行,帮我们越界访问后面下标为10,11, 12的元素

    根据这三个特点,我们不难想到,当i=12的时候,将arr[12]就与i重合了,此时将arr[12]改成了0,也就是将i改成了0,这时循环判断又从0开始了,到下一个12又出现这种情况,周而复始,导致了代码死循环
    当然,这种代码只是特例,是要看环境的,在VS上切换到X64,栈区使⽤的顺序就是相反的,在Release版本的程序中,这个使⽤的顺序也是相反的,也就导致arr[12]和i不会重合,也就不会死循环,最多报一个越界访问的警告

注意:从这个例子看出来,有些代码会根据环境的不同而产生不同的结果,比如栈区的默认的使⽤习惯是先使⽤⾼地址,再使⽤低地址的空间,但是这个具体要看编译器的实现,但是我们牢记一点,只要我们规范写代码,基本上不会出问题,上面例子中也是数组越界访问导致死循环,只要规范写代码,不写出这种越界访问的错误,就会大大减小出错的概率

相关文章:

  • unoredered_mapunordered_set封装
  • R语言VAR模型的多行业关联与溢出效应可视化分析
  • LinkedList添加和删除方法的源码分析超详细
  • JS生成随机数
  • 多通道协议-FTP详解
  • matlab峰值检测
  • 【非常困难】 猿人学web第一届 第10题 js 混淆 - 重放攻击对抗
  • odoo17 footer 异常备忘
  • Python+PyCharm安装和配置(详细步骤)
  • Flutter ListView 实现不同样式 item
  • 【HTML】模拟插头连接断开动画
  • 复杂的编辑表格
  • Oracle SQL - 合并重叠的期间
  • 如何选择最佳路线?
  • sql盲注python脚本学习 (基于bWAPP靶场)
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 【面试系列】之二:关于js原型
  • ECMAScript6(0):ES6简明参考手册
  • JavaScript 基础知识 - 入门篇(一)
  • Puppeteer:浏览器控制器
  • storm drpc实例
  • supervisor 永不挂掉的进程 安装以及使用
  • Travix是如何部署应用程序到Kubernetes上的
  • Vim Clutch | 面向脚踏板编程……
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • webpack入门学习手记(二)
  • windows下如何用phpstorm同步测试服务器
  • XML已死 ?
  • 从零开始的无人驾驶 1
  • 关于List、List?、ListObject的区别
  • 前端相关框架总和
  • 前嗅ForeSpider教程:创建模板
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (含笔试题)深度解析数据在内存中的存储
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (一)Linux+Windows下安装ffmpeg
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .Net IE10 _doPostBack 未定义
  • .NetCore部署微服务(二)
  • .NET命名规范和开发约定
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • //TODO 注释的作用
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • [20171102]视图v$session中process字段含义
  • [20190416]完善shared latch测试脚本2.txt