Delphi应用程序的调试(九)调试技术

这里还要再介绍几个调试技术。大家使用这些调试技术,能使调试程序的工作变得更容易。

OutputDebugString函数

当程序运行时跟踪程序的执行有时对用户是很有帮助的;也许用户希望在不使用断点来暂停程序执行的情况下查看变量的值。使用OutputDebugString函数就能做到这些。这个函数是个使用方便的调试工具,但很多程序员却忽视了它,主要原因是对它介绍的不够。观察下图中Event Log窗口的最后一个入口,这一入口是用下面的代码生成的:

procedure TForm1.btn1Click(Sender: TObject);
begin
  OutputDebugString('In the btn1Click method...');
end;

image

这就是全部要做的。由于Delphi是系统调试器,因此,任何用函数OutputDebugString发送的字符串都会出现在Event Log窗口。可以在代码中任何位置调用OutputDebugString函数。

要查看一个变量的值,必须按格式构成字符串,并把字符串发送给OutputDebugString函数,例如:

image

运行程序后,点击btn1后,Event Log中显示如下:

image

使用OutputDebugString函数可以查看程序运行的整个过程,即使是对实践要求严格的代码段。

 

追踪查找存取违例

当一个程序试图往不属于它的内存写入数据时,Windows会发出一条“Access Violation(存取违例)”出错消息。所有的Windows程序员在开发应用程序时,都碰到过存取违例错误。

Note

在16位Windows中使用术语一般保护性错误GPF(General Protection Fault)。这一术语在32位Windows中也很流行,尽管32位Windows实际上产生Access Violation出错信息,而不是GPF。

不论是初学者还是经验丰富的Windows程序员,要追踪查找存取违例,都是一件困难的事情。但是,随着编写Windows程序的经验积累,程序员会逐渐对存取违例原因的查找产生产生第六感。下面讲述一些线索供大家在查找违例时参考;并不是只有这些情况才会使程序产生存取违例,但它们是最常见的情况。

1、未初始化指针

未初始化指针是已经在程序中声明过的指针,但还未给它赋一个有意义的内存地址值。未初始化指针包含的是随机数据,最好的情况是它指向内存中无关紧要的地址,最坏的情况是它指向用户程序所在的内存中的某个单元,这样会导致不稳定的程序动作,因为每次运行程序时该指针可能指向不同的内存单元。应当在使用一个指针前和指针所指对象被删除之后将该指针设置成nil(空指针)。当存取一个空指针时,程序会停止运行并报存取违例,调试器加亮显示出错源代码行,这样用户就能立即查出有问题的指针。

2、删除前面已删除的指针

删除一个已经被删除的指针会导致存取违例。应该将已删除的指针设成nil(空指针);删除一个空指针是十分安全的。把删除过的指针设置成nil(空指针),再删除这个指针时就保证不会出错。

3、数组覆盖(Array Overwrites)

覆盖一个数组的末尾可引起存取违例。在有些情况下,被覆盖的内存不是关键性的,不会马上显现出问题,但过后不久程序还是因故障而停止运行。当这种情况发生时,用户很可能到程序停止运行的地方去查找故障,但问题实际上是出在程序的其他地方。另外一种情形是:被覆盖的内存很关键,因而程序会因故障而立即停止运行。极端情况下会造成Windows被破坏。

可通过范围检查把数组覆盖的可能性减少到最低程度。当设置检查范围时(缺省设置),编译器会对每一次数组引用作检查,看看看所存取的数组元素是否超出了有效范围。例如,下面这段代码就会导致编译器报错:

image

这段代码中要存取一个数组的30号元素,而此数组只有21个元素。编译器会检查出被存取的数组元素超出了数组声明的范围,并产生一个编译器错误,出错信息如下:

image

但是,检查范围对变量无效。例如,下面的代码就不会导致编译报错:

image

在这段代码中,尽管数组覆盖了9个字节,却不会产生编译错误;这是因为在编译时,编译器不知道变量X的值。

4、程序终止时存取违例

当正常关闭一个程序时发生存取违例,一般都说明堆栈设置的太小。尽管在32位程序中这种情况发生的可能性不大,但在极端情况下也会发生。就像前面讲过的删除一个已被删除的指针,也可能引起程序终止时存取违例。

调试快速提示

除前面给出的许多提示外,还要补充一下提示:

  • 改变窗体的Caption属性,从而不必使用断点就能显示变量。因为在窗体上添加Label组件是件很容易的事情,因此用户可使用Label组件,改变Label组件中的正文来显示变量的值或其他要显示的信息。
  • 启用一个条件断点或数据监视断点来临时减慢程序的运行速度(比如要让程序缓慢运行以便查看程序效果)。当程序运行碰到断点时,要检查断点条件,从而减慢程序的执行。
  • 在程序运行时,使用Evaluate/Modify对话框临时改变一个变量的值。这样,用户可查看不同值对程序的影响,而不必每次重新编译代码。
  • 从主菜单上选择【Run | Inspect】,并在Expression字段输入Self,来检查调试器当前暂停处的类。
  • 使用MessageBeep($FFFF)作为发生指示器,来指示程序执行到达程序中的某一位置。当用参数$FFFF时,它会使PC的扬声器发出嘟嘟声。
  • 从主菜单上选择【Run | Program Reset】菜单项或按【Ctrl + F2】键来终止出错调试过程。
  • 使用中间变量来断开长等式或连锁方法调用,以便用户能更方便地检查结果。
  • 使用ShowMessage,MessageBox或MessageDlg函数来显示程序跟踪信息(ShowMessage函数用来更方便,因为它只有一个字符串参数)。

Caution

如果用户在Windows 95上运行Delphi,则要尽量少使用Program Reset选项。有些时候,使用Program Reset终止应用程序的运行会破坏Windows 95。

但不是所有的Windows 95都会出现这种情况,因此可能不会遇到这个问题。Windows NT发生这种问题的可能性更小,因此在Windows NT中可以放心地使用Program Reset。就其个人而言,本人只在调试的应用程序被锁死时才使用Program Reset。

还有一个提示就是,当调试程序时,可使用一个内存检查程序。第三方的内存检查程序用于检查用户应用程序有无内存泄露。当用户将应用程序投入实际使用时,这类程序能省去许多麻烦。当应用程序存在内存泄露时,在使用该应用程序的过程中就会出问题。早起发现并排除内存泄露,无疑可大大节省用户的时间和精力。

posted on 2012-06-02 09:20  pchmonster  阅读(4600)  评论(1编辑  收藏  举报

导航