1 Debug和Release
在VS编译代码时,我们可以看到左上角会有两个选项,Debug和Release,如图所示:
Debug为调试版本,它包含调试的信息,且不对代码做任何的优化,这样编译会产生debug版本的可执行程序,方便程序员来调试修改。
Release为发布版本,它不包含调试信息,但是会对代码进行各种优化,使得代码在大小和速度上都是最优的,能够让用户很好的使用,通常是程序员在debug版本写完并测试完代码后,满足用户的需求,就将程序设置为release版本,发布给用户体验。
2 VS常用的快捷键
在调试过程中,掌握那些经常使用的调试方法的快捷键能够帮助我们更好的实现代码。
调试经常使用的快捷键如下:(不同编译器的快捷键不同)
Fn+F5:
启动调试,用于直接跳到下一个断点处,一般需要和F9配合使用。
Fn+F9:
创建断点和取消端点(创建断点后该行代码左边会有一个红色的点,即为创建成功),在程序任意位置打上断点后,调试时程序会直接执行到断点处,方便我们观察代码细节。
如果程序有循环,并且我们想观察循环后面的数时,我们可以设置条件断点,程序直接运行到满足条件断点处。
Fn+F10:
逐过程处理,过程可以是单条语句,也可以是一个函数(不会进入函数内部,直接完成函数的调用)。
Fn+F11:
逐语句处理,每次执行一条语句,可以进入函数内部观察细节。
3 监视和内存的观察
3.1 监视(观察变量值的变化)
当我们完成了代码,按Fn加F10开始调试后,可以在调试窗口使用监视。如图所示,随便哪个监视都可以。
在这里我们就可以输入想要监视的变量的名称()
当我们传递一维数组给函数,想要在函数中观察数组的每个元素时,我们会发现,监视只能观察到数组的第一个元素,想要观察到数组的所有元素,我们就需要在数组后面加上想要观察的元素个数。
如图,当我们想在函数中观察数组的前5个元素时,我们可以这样输入:
3.2内存(观察变量在内存中的存储情况)
例如我们想要观察数组的地址,我们可以在地址中输入arr,点击Enter,再确定我们需要看多少列地址,就可以观察到每一个数的地址在哪里了。
4 调试代码举例
在VS 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\n");
}
return 0;
}
我们可能会认为for循环i的值最大为12,超过了数组的最大元素下标9,所以数组会越界访问,但是结果如下:
可以发现,程序进入了死循环。我们肯定会疑惑程序为什么会死循环呢?
这时候就能发挥调试的作用了。
当我们执行完赋值语句后,可以发现数组成功赋值,越界的数组arr[10]和arr[11]都是随机值。
当我们运行到i=9的时候,将数组元素赋值为0都是成功的。
继续往下运行,我们会发现,arr[10]也被赋值为0。
当i=12的时候,执行完第241行代码后,我们会惊奇的发现,i的值变成0了,之后又会进入循环,无法结束,这就是程序死循环打印hehe的原因。
那么问题又来了,为什么i的值会和arr[12]一样同时被赋值为0呢?
我们就会思考,有没有可能i和arr[12]分配的是同一块空间呢?
当我们观察arr[12]和i的地址,会发现,确实它们分配的是同一块空间。
这时我们就需要了解一下内存布局图了。
我们需要知道2个点:
1.栈区地址从高地址向低地址使用,优先分配高地址,由于先创建的i变量,所以i的地址要比数组arr高一些,
2.数组在内存中随着下标的增长,地址由低到高变化,所以数组中越靠后的元素地址越大。
当数组足够大,元素足够多的时候,就有可能覆盖i的空间地址,导致某一个元素的空间和i的空间重合,不同编译器中间空出的大小不一样,这题是巧合,VS刚好是数组和变量中间间隔两个整形的空间,所以i和arr[12]地址重合。
当改变i的值为小于12的数,例如11时,程序就会显示异常,不会死循环打印,所以我们平时在写数组相关的代码时,要多注意给数组分配的空间,要尽量避免数组越界,以免带来不必要的麻烦。
所以,当我们的代码遇到了问题,但是又不知道问题出现在哪里的时候,我们可以多尝试各种不同的调试方法,结合起来使用解决问题,学习更多的调试技巧,能够使我们写代码时事半功倍。