调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序 错误的一个过程。
可以看到调试即debug 可以把de看作一个动词前缀,表示解决/处理bug的意思
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。 Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优 的,以便用户很好地使用。
下方两图为不同模式下对于代码编译链接之后生成的可执行程序大小的区别
所以我们说的调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。(Release版本不可调试)
下面用同一段代码在不同模式下执行结果来说明Release版本到底做了哪些优化(一个例子)
可以看到Release版本下程序执行完毕了,(虽然这个程序数组越界访问了)而Debug版本下程序进入了死循环。死循环的原因:
1. 在程序开始执行之后要为变量在内存中创建空间从而储存数据,而内存分为栈区,堆区,静态区。 局部变量,函数的形式参数等都要存储在栈区中,静态区存储的是静态变量,全局变量等。
2. 栈区的空间使用习惯为:先使用高地址处的内存,再使用低地址处的内存。而这段代码是先创建的i,后创建的数组,也就是说变量i存储的地址比数组高。
3. 数组存储规则:随着下标的增长,存储地址是由低到高的。
4. 就在VS这个环境下,如下图所示数组的最高地址处与i的存储地址处间距2个int型空间。(这个是一定的,在Debug版本下,&arr[9]与&i相差12个字节,就是arr[9][10][11]的空间)(但是不知道在其他程序中是否所有这样的代码都遵循这样的存放规则,后续有了发现再更新)导致了在i=12进入循环之后,arr[12]就是变量i,此时赋值为0。i与arr[12]都变为了0,上方再次循环就不满足退出条件了。
5. 从图二可以看到,Release版本下i与arr[0]的地址一个是188(i)一个是192(arr[0])。变成了i的地址为低地址了,所以arr[12]处也就不再是i了。所以for循环不会进入死循环,这就是Release版本对代码的优化(其中一个优化)
附:下方图片中还指出了VC6.0即GCC中数组和变量i存储位置的比较,间距多少个int型空间是不一定的。
---------------------------------------------------------------------------------------
上图为Debug下,确实是先使用高地址,后使用低地址。且arr[0]与i之间仍然是相差2个int空间
如果是 debug 模式去编译,程序的结果是死循环。 如果是 release 模式去编译,程序没有死循环。 那他们之间有什么区别呢? 就是因为优化导致的。
变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果。(Release下调整之后是紧挨着存储的,即i在下,arr[0]在上,中间无空隙,就是Release的调整)
ps:Debug与Release下代码的反汇编也是不同的
调试时的快捷键
F9 - 创建/取消断点
在程序的某一行处创建断点或取消断点
F5 - 开始调试:(一般是配合F9,F10,F11使用)
当在程序中某处建立一个断点之后,按F5,即开始调试,程序会运行到断点处,后面可以借助F10或F11来进一步调试。若程序中无断点,则程序直接运行到底,这样和Crtl+F5的作用是一样的:即开始执行(不调试)。或者跳转至下一个断点处。
Ctrl+F5 - 开始执行(不调试):
把程序从头到尾执行一遍,不调试
F10 - 逐过程
代码一行一行的进行,遇到函数也会直接执行完函数,不会进入函数
F11 - 逐语句
代码一行一行的进行,遇到函数也会进入函数,在函数内一行一行地进行
1. 监视:用来查看变量或数组的值
2. 自动窗口 : 用来查看变量或数组的值,自动生成变量(当前存在的变量),但是看起来不如监视清楚。
3. 调用堆栈:通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。
如下图所示(但是这个功能目前来看并不是很常用)
main函数调用game , game调用check_mine , check_mine调用check_pro。
4. 内存
可以查看到内存中的存储情况,每个字节存储的是什么数据,比较细。
5.反汇编: 可以切换到汇编代码。
6.寄存器: 可以查看当前运行环境的寄存器的使用信息(没标出来)
使用assert,const等,这样在代码出错时,易于找出出错的位置,易于排错,也能提高代码健壮性
如:
模拟实现strcpy