前言
我们在平时写完程序之后,可能因为当时的考虑不周或粗心大意,程序内隐藏着一些未被发现的缺陷或问题,这时候就出现了程序漏洞,也叫bug,bug的的名字来自创始人格蕾丝·赫柏,大家对这个名字的由来感兴趣的话可以自己去查一查
在发现问题之后,下一步就是找到问题,并修复问题。这个找问题的过程就简称调试,英文叫debug(消灭bug)的意思。
一、Debug和Release在这里插入图片描述
在VS上编写代码的时候,就可以看到Debug和Release两个选项,它们分别是什么意思呢?
Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。程序员在写代码的时候,需要经常性的调试代码,就将这里设置为debug,这样编译产生的是debug版本的可执行程序,其中包含调试信息,是可以直接调试的。
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。当程序员写完代码,测试再对程序进行测试,直到程序的质量符合交付给用户使用的标准,这个时候就会设置为Release,编译产生的就是release版本的可执行程序,这个版本是用户使用的,无需包含调试信息,这里打开看他们对应版本在文件中的存储大小就能明显的看出区别:
Debug
在这里插入图片描述 Release
在这里插入图片描述
分别在对应版本运行之后,就可以在路径下看到这两个版本下生成的exe文件,对比可以看到从同一段代码,编译生成的可执行文件的大小,Release版本明显要小,而Debug版本明显更大。
二、VS调试快捷键在平时我是怎么调试代码的呢?
3.1 环境准备首先是环境准备,需要一个支持调试的开发环境,要把VS设置为Debug。
3.2 调试快捷键调试最常用的几个快捷键:
F9:创建断点和取消断点,断点的作用是可以在程序的任意位置设置断点,打上断点就可以使得程序执行到想要的位置暂停执行,这里就能想到,在大型的程序里,这样肯定是很方便的。
在这里插入图片描述
即这个红色的小点,按一下F9打上断点,再按一下F9取消,接下来我们就可以使用F10,F11这些快捷键,观察代码的执行效果。
还有一种条件断点:满足这个条件,才触发断点
F5:启动调试,经常用来直接跳到下一个断点处,一般是和F9配合使用,注意F5在执行过程中停到执行逻辑的下一个断点处,在循环中就是走一次循环来到断点处,这里是什么意思呢?
上图展示
在这里插入图片描述
这里F5已经来到了12行,再按F5能到达16行吗?是不能的,这里循环要执行10次,F5在这10次中始终走一次循环再来到12行,配合监视窗格就可以发现按F5的次数,i随之在变化,想要来到16行需要把12行断点取消掉。
F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11:逐语句,就是每次都执行一条语句,这里区分F10和F11就是这个快捷键可以使我们执行逻辑进入函数内部。在函数调用的地方,想进入函数观察细节,必须使用F11,如果使用F10,直接完成函数调用。
CTRL+F5:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
这里区分F5和CTRL+F5就是:
F5遇到断点会停下来的
CTRL+F5遇到断点也不理睬
CTRL+F(find):此快捷键在查代码的时候使用特别多
在这里插入图片描述
可以自行下去试用其功能
当然这些功能在调试窗口也是有的
在这里插入图片描述
在编辑窗口还有一些其他的功能
在这里插入图片描述在这里插入图片描述
这里的自动窗口和局部窗口类似,都是对某一数据在代码运行中的表现进行观察,不过VS会根据上下文的环境把数据放进去进行观察,但不太好用,它不仅会自动添加数据,还能自动删除数据
在这里插入图片描述
反汇编即将C语言代码翻译为汇编代码,static那篇文章里有写。
VS更多快捷键了解:https://blog.csdn.net/mrlisky/article/details/72622009
VS更多快捷键了解:
这是另一个博主的VS快捷键整理文章,我觉得还是非常适用的,但有些快捷键随着版本的迭代还是有一些变化的。
三、监视和内存观察监视这里就不说了,再熟悉不过了
3.1 内存在觉得监视窗口看的不够仔细的情况下,可以观察变量在内存中的存储情况,打开流程如下(补充:无论是监视还是内存的观察,都需要在调试情况下观察,即按f10后):
在这里插入图片描述
那么内存窗口应该怎么看呢?
在这里插入图片描述
这里自动的意思就是VS会根据窗口宽度,自动调整内容宽度,也可以根据自己的需要改为一行显示几个字节,中间这一栏内存本身是以二进制存在的,但二进制展示的信息太多了,所以这里用16进制。
在这里插入图片描述
在这里的地址栏输入:arr.&num,&c这类地址,就能观察到该地址的数据了。
四、特殊案例1在VS2022、X86、Debug的环境下,编译器不作任何优化的情况下,下面这串代码执行的结果是啥?
代码语言:javascript复制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;
}按照常规的思路来说,这里是超出数组范围了,属于数组越界,程序运行会崩溃,发生报错
在这里插入图片描述
然而实际情况却是死循环了,这里就需要通过调试来看看为什么会这样了。
在这里插入图片描述
可以发现,这串代码除了把在范围内的下标为0-9的元素改为0之外,还可以把下标为10和11不存在的元素的值也改为0,这就足以看出在不同的环境下,程序的运行结果可能是不同的,而且i的值与arr[12]的值好像是某种关联关系,arr[12]的值随i的值变化而变化
在这里插入图片描述
如果在按f10,让i变为13的话,它理应跳出循环,但是这时候i重新归0了,这可就糟糕了,它就要再重复的走循环,这就是死循环现象的原因。
那么这里i为什么会随着arr[12]的值的变化而变化呢?这里就有一种情况,这俩在内存中是同一块空间,可以输入他们的地址看一下
在这里插入图片描述
这又是为什么呢?
其实这是一种巧合,单独拿出来说一下
上面程序调试的内存布局如下
在这里插入图片描述
这里低地址和高地址是假设出来的
栈区内存的使用习惯是从高地址向低地址使用的,所以变量i的地址是较大的。arr数组的地址整体式小于i的地址的数组在内存中的存放是:随着下标的增长,地址是由低到高变化的。这样就能理解这个图的意思了
照着图来看,随着数组下标的增长,往后越界就有可能覆盖到i,这样就有可能造成死循环。
那么i与arr的内存之间有没有空隙呢?
这个空间多大是不确定的,这里只是恰好空了两个整型
还有人问,它越界了为什么没有报错呢?
这里100%是越界了,但此时程序正忙着死循环呢,都没有机会报错
这里确实是一个巧合,在不同的编译器下,i和arr中间空出的空间大小是不一样的
例如在VC6.0中,i和arr中间就没有空隙
在gcc环境中,i和arr中间空了一个整型
有一种解决方案就是基于前面说的死循环的原理,把i和arr的位置互换一下,解决空间重合的问题,就可以正常报错了。
这也是与它的特殊环境有关,在X64环境下也可以正常报错,也是因为x64环境下会对代码进行相关的优化,虽然栈区的使用习惯是图中所示,但编译器会对代码进行相关的调整,即将i和arr的顺序调换,在Release版本中,这个使用的顺序也是相反的,甚至连程序的报错都优化掉了。。。。
这里展示这几种情况地址打印出来的效果
X86 Dubug
在这里插入图片描述
X64 Debug
在这里插入图片描述
X86 Release
总结这篇文章本来是要6月1日发出来的,但是当时因为有一些事情干扰,今天也是熬夜补发了,喜欢的靓仔靓女不要忘记一键三连哦,我个人觉得这篇文章阅读门槛是极低的,而且非常实用。