使用VC6.0对C语言程序进行调试的基本手段
(1)设置固定断点或临时断点
所谓断点,是指定程序中的某一行,让程序运行至该行后暂停运行,使得程序员可以观察分析程序的运行过程中的情况。这些情况一般包括:①在变量窗口(Varibles)中观察程序中变量的当前值。程序员观察这些值的目的是与预期值对比,若与预期值不一致,则此断点前运行的程序肯定在某个地方有问题,以此可缩小故障范围。例如以下程序是计算cos(x)并显示,运行时发现无论x输入为多少,结果都是0.046414。
#include <stdio.h>
#include <math.h>
void main()
{
int x;
printf("Please input x:");
scanf("% d", &x);
printf("cos(x)=%f\n", cos(x));
}
在该程序中,若你没有看到问题——程序较长、较复杂时很难看出问题所在,则应该使用调试手段定位故障位置。
②在监控窗口(Watch)中观察指定变量或表达式的值。当变量较多时,使用Varibles窗口可能不太方便,使用Watch窗口则可以有目的、有计划地观察关键变量的变化。
③在输出窗口中观察程序当前的输出与预期是否一致。同样地,若不一致,则此断点前运行的程序肯定在某个地方有问题。
④在内存窗口(Memory)中观察内存中数据的变化。在该窗口中能直接查询和修改任意地址的数据。对初学者来说,通过它能更深刻地理解各种变量、数组和结构等是如何占用内存的,以及数组越界的过程。
⑤在调用堆栈窗口(Call Stack)中观察函数调用的嵌套情况。此窗口在函数调用关系比较复杂或递归调用的情况下,对分析故障很有帮助。
(2)单步执行程序
让程序被一步一步(行)地执行,观察分析执行过程是否符合预要求。例如,以下程序预期的功能是从键盘上读入两个数(x和y),判断x和y是否相等,相等则在屏幕上显示x=y,不相等则显示x<>y。这是要求实现的功能,但程序实际的运行状况却是:无论输入什么,都会在屏幕上显示x=y和x<>y,程序肯定有问题,但表面上看却可能找不到问题所在,使用单步执行,则能定位故障点,缩小看的范围。例如,在单步执行的过程中,若输入“2,3”,发现x和y的值的确变成了2和3,此时按道理不应执行“printf("x=y\n");”,但单步跟踪却发现被执行了,因此多半问题出在“if (x = y)”。#include <stdio.h>
void main()
{
int x, y;
printf("Please input x, y:");
scanf("%d,%d", &x, &y);
if (x = y)
{
printf("x=y\n");
}
else;
{
printf("x<>y\n");
}
}
在单步执行的过程中,应灵活应用Step Over、Step Into、Step Out、Run to Cursor等方法,提高调试效率。建议在程序调试过程中,记住并使用“Step Over、Step Into、Step Out、Run to Cursor”等菜单项的快捷键,开始时可能较生疏、操作较慢,但坚持一段时间就能生巧、效率提高。
(3)使用断言
断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。使用断言时,必须在程序的开头加上:
#include <assert.h>
①可用断言来确认函数的参数。示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,以防止其他人调用本函数时使用空指针作参数。代码如下:
int exam_fun( unsigned char *str )
{
assert(str != NULL); // 断言“指针不为空”,若“空”(断言不成立)则报错
... //other program code
}
②可用断言来确认是否发生了不该发生的情况。示例:以下程序段运行结果有错,检查起来很困难而且搞了很久都不知是什么地方有问题。因此,建议分析程序的正常运行情况应该是什么,运行过程中是否出了异常,针对所有(或关键状态)应当正常的情况,使用断言,就很有可能发现异常原因,且调试效率很高。针对该程序段,我们断言(断定)变量i的取值应该为“i>=0 && i<SIZE”且较关键,但在运行过程中是否有可能被无意修改(例如其它变量越界)而超出范围呢,就可使用断言检查是否发生了这样的情况。
for (i=0; i<SIZE; i++)
{
... //other program code
assert(i>=0 && i<SIZE); // 断言“i的正常取值范围”,若断言不成立则报错
array[i] = i;
... //other program code
}
断言不成立时(一出现异常),系统将立即报错,此时可进入程序调试状态,检查程序的运行情况。
(4)与调试相关的操作菜单:Build菜单
Compile:快捷键Ctrl+F7。编译当前处于源代码窗口中的源程序文件,以便检查是否有语法错误或警告,如果有的话,将显示在Output输出窗口中。Build:快捷键F7。对当前工程中的有关文件进行连接,若出现错误的话,也将显示在Output输出窗口中。
Execute:快捷键Ctrl+F5。运行(执行)已经编译、连接成功的可执行程序(文件)。
Start Debug:选择该项将弹出子菜单,其中含有用于启动调试器运行的几个选项。例如其中的Go选项用于从当前语句开始执行程序,直到遇到断点或遇到程序结束;Step Into选项开始单步执行程序,并在遇到函数调用时进入函数内部再从头单步执行;Run to Cursor选项使程序运行到当前鼠标光标所在行时暂停其执行(注意,使用该选项前,要先将鼠标光标设置到某一个你希望暂停的程序行处)。执行该菜单的选择项后,就启动了调试器,此时菜单栏中将出现Debug菜单(而取代了Build菜单)。
(5)与调试相关的操作菜单:Debug菜单
启动调试器后才出现该Debug菜单(而不再出现Build菜单)。Go:快捷键F5。从当前语句启动继续运行程序,直到遇到断点或遇到程序结束而停止(与Build→Start Debug→Go选项的功能相同)。
Restart:快捷键Ctrl+Shift+F5。重新从头开始对程序进行调试执行(当对程序做过某些修改后往往需要这样做!)。选择该项后,系统将重新装载程序到内存,并放弃所有变量的当前值(而重新开始)。
Stop Debugging:快捷键Shift+F5。中断当前的调试过程并返回正常的编辑状态(注意,系统将自动关闭调试器,并重新使用Build菜单来取代Debug菜单)。
Step Into:快捷键F11。单步执行程序,并在遇到函数调用语句时,进入那一函数内部,并从头单步执行(与Build→Start Debug→Step Into选项的功能相同)。
Step Over:快捷键F10。单步执行程序,但当执行到函数调用语句时,不进入那一函数内部,而是一步直接执行完该函数后,接着再执行函数调用语句后面的语句。
Step Out:快捷键Shift+F11。与“Step Into”配合使用,当执行进入到函数内部,单步执行若干步之后,若发现不再需要进行单步调试的话,通过该选项可以从函数内部返回(到函数调用语句的下一语句处停止)。
Run to Cursor:快捷键Ctrl+F10。使程序运行到当前鼠标光标所在行时暂停其执行(注意,使用该选项前,要先将鼠标光标设置到某一个你希望暂停的程序行处)。事实上,相当于设置了一个临时断点,与Build→Start Debug→Run to Cursor选项的功能相同。
Insert/Remove Breakpoint:快捷键F9。本菜单项并未出现在Debug菜单上(在工具栏和程序文档的上下文关联菜单上),列在此处是为了方便大家掌握程序调试的手段,其功能是设置或取消固定断点——程序行前有一个圆形的黑点标志,表示已经该行设置了固定断点。另外,与固定断点相关的还有Alt+F9(管理程序中的所有断点)、Ctrl+F9(禁用/使能当前断点)。