掌握C++Builder的除错艺术-第二篇(完整版)之一

    技术2022-05-11  141

     

     

     

    Mastering the Art of Debugging in C++BuilderArticle 2 - Watching it Closely

    掌握C++Builder的除错艺术

    第二篇-近距离观察

    (1) 

    1.        调试可执行程序前的准备

    2.        工程选项

    3.        设置断点并闯入可执行程序

    4.        察看储存在变量中的值

    5.        使用Watches(观察)

    6.        使用Inspectors(巡视器)

    7.        使用Evaluate/Modify(求值/修改)

    8.        Stepping Through, Over and Around Blocks of Code

    9.        Stepping的类型

    10.   Stepping的注解

    11.   其他提示

    Okay,(再小小准备一下)现在开始追踪、搜索经过前次的努力后仍然躲在代码中的bug的时候了,也就是开始跟踪前一篇文章代码里标记过的bug/异常。首先是准备阶段。

    调试可执行程序前的准备

    在我们开始调试可执行程序前,我们需要确保一些设置在大多数情况下的正确性。我将会一条接一条的过一遍,并简单解释一下为什么必须那样做。(如果您对有些东西感兴趣的话,按下帮助按钮,会有许多更详尽的内容)。现在就开始吧,先打开Project|Options选项。

    工程选项

    首先我们在"Compiler"(编译)标签处停下。您只需简单的单击"Full debug"(完全调试模式)按钮,我们所需的绝大多数的其余设置就已经搞定了。将"Code optimization"(代码优化)设为"None"(无)总是件好事,这样做实际上告诉编译器:所有的事情都已做好,只需产生机器码就行了。而不要为了提高一点点运行速度尝试进行其他的智能优化。(当然,一切都完成之后,您可以打开此项。)这样做的好处是大大降低了我们调试的难度。因为程序中的代码与我们书写的一样,没有被编译器优化过。在"debugging"(调试)面板中,将"Debug information"(调试信息)选上(点一下),并且必须设置为"Line number information"(行数信息)。我还建议将"Disable inline expansions"(禁用内联扩展)一项选上。内联扩展对发布的代码来说很好,但调试时最好还是关掉此项,他只会让您更头痛。

    然后是"Pascal"标签,尤其在您的工程里连接了Pascal单元或使用了基于Pascal的VCL控件时(若您拥有其Pascal源码时,编译器会自动使用此节中的设置重新编译)。这里您必须将"Optimization"优化选项禁用,然后通常我会将"debugging"(调试)部分的所有选项选上(打钩)。

    接下来是"Linker"(链接)标签,我们需要选上"Create debug information"(生成调试信息)。"Use dynamic RTL"(使用动态RTL)以及"Don’t generate state files"(不要生成状态文件)是造成麻烦的选项。我通常都会使用状态文件(这样允许增量链接,但会在编译目录下产生一个4倍于可执行程序或更大的文件),换个角度来说,这样会增加链接大工程时的速度。而使用dynamic RTL本身就是个争论,尚有很多赞同和反对的讨论。

    下一个是"Directories/Conditionals"(路径/条件)标签。在这里我们想要设定"Directories/Conditionals"(调试源路径)的值。我们永远都应将此处设定为$(BCB)/source/vcl,但是如果您有任何其他的组件附加的话,通常将它们的路径也加上是个好主意(路径与路径之间用”;”分隔或者您可以用按下…按钮弹出的对话框来设定它们)。

    最后也是最重要的设置是在"Packages"(程序包)标签上。根据所有恰如其分的调试经验您必须禁用"Build with runtime packages"(带运行时程序包编译)。这么做的原因是程序包本身不包含而且不能包含调试信息。这样做,也许不利于您跟踪标准的VCL代码,例如想看清楚VCL函数y中参数x是如何起作用的时候。但是大多数时候,您这么做将会发现调试器将您的绝大多数“症状”归结给VCL,尽管“病因”就在您的源代码中(或在其他的组件中(这已经在我们所有人身上发生了))。一旦您发布您的正式版本时,您可以决定是否使用程序包。(译者注:程序包的本质是一个特殊的DLL,不带运行程序包(静态)编译可以让您的程序脱离Cbuilder独立运行。),但在调试时,请禁用掉。按下OK按钮,我们已经准备好啦。下一个对话框只需打开一次,但最好还是来检查以下我们在这里的设定是否正确。好了,打开”Tools|Debugger Options…”吧。

    对话框最下方的"Integrated debugging"(集成调试器)选项是关键所在。确信已经打上钩。按下OK按钮准备编译可执行程序吧。我建议重新来一次彻底的编译(选择Project|Build All),如果您修改过您的设置的话(尤其是改变”building with packages”方式后)。这将保证我们的所有程序单元按照我们所希望的那样被编译。

    设置断点并闯入可执行程序

    象您所见过的其他任何一款调试器一样,C++Builder提供强大的断点设置功能。基本上,断点是指代码中的一个点,程序执行至此停下(与退出不同,这只是执行中的暂停)并将控制权交还给调试器。设置一个断点相当容易。只需在您想要设置的程序代码行左侧的灰色槽形区域点击,您会看到一个红点出现,这一行也会变红。程序运行到这一点就会暂停,将控制权交还给调试器。

    您也许会问如果我不想每次都停下来呢?当然可以,而且还很容易做到,这取决于您暂停程序的标准是什么?(译者注:条件断点)。在刚才那个断点(红点)上右击鼠标并从弹出菜单上选择” Breakpoint Properties”(断点属性)。此处可以设定两种属性"Condition"(条件)和"Pass Count"(通过次数)。Condition(条件)属性太方便了。您可以利用if()语句输入几乎是任意的条件。但请牢记条件中的所有变量,对此断点都应是可见的。条件属性并未被编译器编译到执行程序中,而是在运行时,当程序运行至断点暂停后,检查断点的条件是否满足。条件为真,停下,否则让程序继续运行。另一个属性"Pass Count"(通过次数)也很容易理解。断点将被通过Pass Count次后停下。结合使用这两个属性,在调试您的代码时,您可以设定非常严格的断点。

    还有一件要牢记的是,当您在调试器中发生异常时,会以产生异常处的那一行代码上的断点的形式出现。这种情况很容易制造。一旦您得到一个异常后应做的步骤我会在以后展示如何在堆栈中回溯并跟踪找出异常发生的真正原因(如引起异常产生的那一小片代码)。

    另一个要牢记的提示是当您运行您的程序时,代码窗口左侧有蓝点的任意一行都可以设成断点。所有非法的断点将会变为红点中带一个黄色的小叉,这一行代码也会变成黄褐色。合法的断点则变为红点中带一个绿色的小钩。运行时,您可以设置/修改任意一点,断点立即生效而无须重新编译。

    察看储存在变量中的值

    一旦您的程序在您的断点处停下后,该做什么?有一件事您想做而且必须做的,那就是察看储存在您程序中的各种变量真实的值。这部分内容涉及的方面很多,您一定要坚持,忍受这些枯燥的东西。幸运的是当您看完这些,您一定会对调试器这部分最强大的功能有些新的理解。有许多种方法可以察看变量的值,主要要根据您的目的来决定。我会从察看当前函数的Local Variables(局部变量)开始把他们都讲完。

    察看局部变量没有太多可以讲的。只需点击”View|Debug Windows|Local Variables”,或按下ctrl-alt-L将会弹出一个窗口,显示了当前函数的局部变量。窗口中的变量将会随您单步向下执行或回溯的函数体的更新而更新。

    使用Watches(观察)

    下一步您可以通过设定一个variable watch(变量观察)来察看程序中的变量。就象它的名称所表达的,观察一个变量并将其值显示在变量观察窗口中(点击"View|Debug Windows|Watches"或按下 ctrl-alt-W)。您可以通过两个途径来添加一个观察,第一种是在代码窗口中高亮选择您要观察的变量或表达式(是的!它可以理解并对绝大多数简单表达式求值,比如(i*j)+05 或者 SomeVector[i].Name)并右击鼠标,选择"Debug|Add Watch at Cursor"或按下ctrl-f5,就会加入观察窗口。如果必要,同时会打开观察窗口

    您还可以通过在观察窗口的空白处双击来添加。这时会弹出添加watch对话框,"Expression"(表达式)域的意思无须多说,但另几个域我想解释一下,它们也同样方便。

    "Repeat count"(重复值)用于您观察一个已知长度的数组变量(比如一个blah[50]数组)。您要将Expression(表达式)设为数组的名字(本例中是blah)。"Repeat count"设为数组的元素数量(本例中是50)。然后就会显示数组的每个元素(如:blah[0], blah[1], blah[2]…)。

    "Digits"(小数位数)用来设定显示十进制浮点数的小数位数的。下面的点选集合是用来强制设定变量的显示类型的(将无符号长整数显示为十六进制格式)。还有一点要特别说明的是,如果您在watch窗口中用鼠标右击一个watch后的弹出菜单上会出现"Break When Changed"的选项,这将在变量上设定一个断点,在此变量发生变化时会暂停程序。

    使用Inspectors(巡视器)

    巡视变量是察看变量中的数据的第三种办法。也几乎是观察完整的类的数据的最佳方法。可以有两种方法来巡视一个变量。第一种是在local variable window(局部变量窗口)中,双击一个变量,将会弹出"Debug Inspector"(调试巡视器)窗口,里面显示了这个变量所有的"Data" (variables) (数据(变量))、"Methods" (functions)(方法(函数))和"Properties"(属性)。如果这是个简单数据,将会显示此变量的名称及其中的值。(译者注:如果是数组呢?真不错!)

    您会注意到,Debug Inspector(调试巡视器)很象property editor(属性编辑器)。当然如此,更加重要的是,事实上您可以在运行时实时改变这些值!!!小心使用啦!改入坏值的结果会让您有说不出来的悲痛。巡视器的这个能力可用来快速测试(假设的)游戏关卡(译者注:好像FPE,GM),而不用有编译-运行-修改-编译-运行的循环。

    (举例巡视Form1)在properties(属性)页上,您将会看到某些属性实际上并没有显示其的值,而是显示了{read=,write=}。如果这些值可以被赋值的话,当您在此区域单击后,您会注意到一个"?"按钮出现在属性的右侧。单击这个按钮将会系统执行适当的函数来尝试取回属性的值。我们可以在这儿举个例子-就举Form1的MDIChildCount的属性吧。在MDIChildCoun的属性值区域上单击,在按下"?"按钮,哇,0(正是非-MDI的程序的指定值)。调试巡视器强大的能力并未到此为止。在巡视器的成员变量的适当区域双击可以打开成员变量的巡视窗口,提供与您开始打开窗口一样的能力。

    巡视器窗口的另一个有用的功能是从对象继承的能力。这可以在通过在适当区域上右击选择"Descend"(继承)来做到。继承的结果是产生了一个新的变量。您会注意到顶部的下拉List box中的变量名称已经换成新的变量名了。您可以直接在ListBox中切换巡视的变量。这使得在对象的不同部分快速切换变得非常简单,而不会让大大小小的巡视器窗口扰乱您的工作空间。

    有一点要牢记的是,如果您离开函数,或者离开变量的作用范围,调试巡视器会失去对变量的跟踪。若您需要再次察看的话,请重新设置巡视器。但是您在当前函数的代码中单步运行的话,巡视器会自动刷新。

     

    版权说明

    国内的网站上,有许多关于C++Builder的内容,但多以软件、组件为主。论坛里也大都不能令人满意,很空虚的感觉。书籍又都昂贵,内容却有抢钱之嫌。对银子不足的初学者、自学者关爱不够,因而想尽自己的绵薄之力。

    文中的所有资料都是从国外网站上收集而来。因为E文不方便,所以翻成中文。因为English和计算机都不是非常好,文中的错误在所难免。若大家觉得有用的话,我计划不断搜集翻译一些有用的东西。

    有任何意见和建议请mailtocker@sina.com

    您可以随意复制、分发、下载此文档。但未经本人同意,您不可以截取、改动本文片断,或用本文谋取任何形式的利益。

     

    史平洋

    2001.2


    最新回复(0)