说明: 文章的名字可能取的比较大,毕竟本人能力有限,掌握的知识比较肤浅,写出的东西可能没有多大的价值。但是看到一些初学者经常问一些太过简单的问题,我终于忍不住想写点什么。通过与很多初学者交流,我发现他们共有的一个弱点是不会调试。很多人根本不知道调试是怎么回事。其实很多问题,只要会调试都能自己解决。
签于此,我就根据自己在程序开发中积累的经验写一点关于 VC6.0 下调试的基本知识和技巧,希望对初学者有点帮助。由于本人的能力有限,文章中可能会有很多不周到之处,希望读者谅解,同时提出批评和指正,以达到共同提高的目的。
测试程序: 为了有个直观的认识和方便学习,我将通过具体的实例程序进行介绍。本文选取的是基于 MFC 的单文档程序,程序名称为 MyDebug 。文章会列出本人所了解的错误和调试技术。
常见错误: 初始化错误、缺少头文件、少 lib (链接错误)、少 dll (运行错误)、类型不匹配(编译错误)、断言错误等等(以后会继续扩充)。
一、调试工具栏介绍
当进入调试状态时,工具栏上会出现如下的工具条。
如果不出现上述工具条,可以进行如下操作调出该工具条。
1 )选择菜单栏的 Tools 菜单项,出现如下窗口
2 )选择 Toolbars 选项卡,如上图,然后在 Debug 打上勾,就 ok 了。
如上图,进入调试状态后,在工具栏的空白处点击右键会弹出上图菜单。在调试的时候重点介绍椭圆区域内的几个选项。
可能很多人不明白,不是说调试嘛,干嘛要说这些!呵呵,其实我也觉得没必要,但是我又担心对于初学者来说,这些可能会很麻烦,所以还是先简单介绍一下。先给读者一个整体的认知,做到心中有数。至于上述选项有何功能,本文后续内容会逐一到来。
二、开始第一个调试工作
进行调试的第一步就是要设置断点。如何设置断点呢?打开一个 VC 程序,在工具栏中可以到这样的工具条 。如果没有可以这样操作 Tools->Customize->Toolbars 标签,然后选择 Build MiniBar ,这样就 ok 了。
设置断点:把光标放在程序的某一行如放在 CMyDebug 的构造函数中,按 F9 或者鼠标左键单击手型按钮,设置后如下图:
设置断点后就可以调试运行了。按 F5 或 就进入调试运行状态。如果按照上述操作,可以到程序停在断点处。如下图:
停止调试的方法有很多,如点击编译选项或 Debug 工具栏上的停止调试选项 8 。
好了到此可以给出一个小结,可能读者会觉得不齿,这说的是什么啊,到这就小结了?根本什么内容都没说嘛!确实还没说到重点,但是在这里给出一个小结,我觉得是有必要的。因为本小结已经讲述了一个完整的调试的基本步骤。
三、常见错误及调试技术
好了,现在开始说些实用的东西。到此,假设读取已经了解调试的基本知识和步骤。下面的内容可能会忽略一些细节,主要讲解常见的错误以及调试技术。
很多程序编译和连接的时候都没有问题,但是只要运行就会出错,而且往往还找不到错误在什么地方,即使用调试运行,也不知道错误在什么地方,因为调试运行时,光标会停在一些看不懂的代码里。对与这类的错误,其实很好调试。这类错误,本人称之为开始运行错误或初始化错误。
这类错误一般发生在初始化函数里或构造函数里。调试这种错误需要用到第一章第二小结介绍的“右键弹出调试菜单 ”的 Call Stack 选项。
为了说明问题,我会故意在程序中制造错误,然后说明如何通过调试的手段找到这个错误。
为了制造一个开始运行错误,可以在 CMyDebugView 的构造函数添加如下代码(注意:先不加任何断点):
CMyDebugView ::CMyDebugView()
{
CString str="abcd";
char a=str[5];
}
按如上方式书写代码后,调试运行(直接运行肯定是不行的)会出现如下结果:
对于初学者来说,一看到这个问题,可能就懵了!这是为什么呢!其实很多问题都会造成这样的错误。我们先不管这些了,接下来点击重试 (R) 按钮(点击其他两个也可以 ,但是找不到错误,所以还是按照我说的做吧! ^_^ )。点击该按钮后会出现如下结果:
这是什么代码?我可没写过这样的代码啊!确实,我们并没有写过这样的代码,但是这样的代码是从哪里来的呢?这个不要担心,我们可以先不管它。如果想知道,我可以解释一下。对于这段代码,是 MFC 自己提供的,是 CString 的一个成员函数。
好了,遇到这个问题,我们怎么解决呢?既然这段不是我们的,那我们想办法找到我们自己写的代码。好!下面教你如何找到自己的代码。
还觉得刚才说的 Call Stack 选项吧!对,就是用它。在工具栏空白处右击会弹出一个快捷菜单,如下左图:
左图 右图
选择 Call Stack ,点击鼠标左键,出现上右图。上右图里会出现很多函数名。其中有很多是我们认识的,也有很多是我们不认识的。从光标出开始往下找,找到第一个我们认识的函数名,如上图红色椭圆标示的函数名,双击该函数,光标会跳转到改函数的函数体,有经验的读者会发现,光标所在的位置,往往就是错误发生的位置。好了,双击函数名会得到如下图:
好了,到了这一步我们就不难发现错误了,仔细看一下, str 的长度为 4 ,而 str[5] 显然超过字符串的长度,当然会出现错误。
注意:往往“开始运行错误”没有这么简单,但是原理就是这样的,这需要读者自己多多练习。好了,第一个错误就说到这里。
顾名思义,运行结束错误,是当程序运行结束后关闭程序时出现的错误。可能很多读者会说,程序都运行完了,出现错误就出现呗,反正不影响程序的运行。其实这种观点是错误的!一个完整的程序是不允许出现这样问题的!即便有些时候不影响程序的运行,但总会让人觉得讨厌。
这种类型的错误和 3.1 节介绍的错误很相近,一个是在程序运行时出错,一个是在程序结束时出错,这两种错误都让人摸不到头脑。但是,我告诉你,这两种错误其实是最好调试的。“运行结束错误”一般发生在析构函数或资源释放的函数里。通常情况下都是内存释放时出现错误。
这类错误常出现在析构函数中,一般是由释放内存空间造成的,因此,本节先构造一个简单的内存空间释放错误。其它类似的资源释放错误有可能出现在程序的其他地方,不一定都在析构函数里,读者要学会变通。
CMyDebugView ::~CMyDebugView()
{
int row=10,col=20;;// 动态数组的函数和 列数
int **array=NULL ;// 动态整型数组指针
// 动态开辟一个二维数组
array = new int *[row];
for (int i=0;i<row;i++)
{
array[i]=new int [col];
}
// 开辟空间一定要记着释放空间
for (i=0;i<row;i++)
{
delete []array[i];
}
delete []array;
// 资源释放后,试图访问该资源
int b=array[0][0];
}
正常运行程序一点都没有问题,但是当关闭的时候就会出现问题了。遇到问题,首先想到的是调试!调试运行程序,然后点关闭程序,这时我们会遇到如下的错误:
错误很奇怪,不知道是怎么回事,没关系,点击确定。光标会自动跳到如下左图所示位置。
左图(错误位置) 右图(变量值查看)
既然光标跳到此处,说明此处肯定存在问题。为了便于观察,我们把 array 这个变量拖放到“ Watch ”这个窗口里,如上右图。可以看到红色区域的值有点不正常(有经验的程序员能看出来)。这样就确定确实是这条语句出错了,再仔细分析,原来是 array 的空间已经释放,而程序还试图访问这个空间,显然这是不对的。
上面的步骤中提到了“ Watch 窗口”,该窗口是做什么用的呢?好,在这里有必要做个说明。“ Watch 窗口”是用来在调试状态下,查看变量值的,如果关注某个变量或数组的值,可以把这个变量名直接拖放到 Watch 窗口里,就可以查看该变量的值。通常包含几个子窗口,如下:
“ Watch 窗口” Context 窗口
“ Watch 窗口”是在调试状态下才可以看到,如果没有,在调试状态下,鼠标右击工具栏,会出现如下菜单:
相信这个菜单应该不陌生了吧。点击红色区域就可以了。既然提到了“ Watch 窗口”,理所当然地要提“ Context 窗口”,该窗口用于显示上下文变量。关于这个窗口的使用,读者可以自己琢磨一下。如果调试状态下没有这个窗口,操作类似上面的方法,唯一的区别就是点击上图中的绿色区域选项。