C语言第四篇调试Word文档格式.docx
《C语言第四篇调试Word文档格式.docx》由会员分享,可在线阅读,更多相关《C语言第四篇调试Word文档格式.docx(10页珍藏版)》请在冰豆网上搜索。
不过,虽然程序调试的技巧和能力是应当在实际的编程过程中来掌握的,但在此之前对调试方法和相关知识作一些了解是非常有必要的。
由于手动跟踪相对简单,因此以下主要介绍程序跟踪和交互式调试方法。
二、程序跟踪
一个程序需要调试,往往是由于在程序运行中出现死锁、程序不响应,或者是程序运行结束却输出不正确,或者是程序无缘无故中止却没有任何出错信息或异常。
这些情况与编译错误和链接错误不同,编译错误和链接错误分别由编译器和链接器发现,并且一般能大致判定出错的原因和位置,有些甚至能非常准确的定性错误产生的原因,因而能较容易的发现和处理。
而调试时的异常情况常常是没有任何系统信息可以帮助你的,对于错误在何处发生,你也可能有线索,也可能没有线索,取决于你对语言的了解、对程序流程的了然于胸,因此,调试是比处理编译和链接错误更困难的事情,需要更高的技巧和水平。
以下是一个最简单的、
intmain()
{
intarray[5]={0,1,2,3,4};
array[5]=5;
//下标越界
return0;
}
也是大多数初学者最常犯错误的、需要调试的程序。
这个程序由于数组下标越界,从而出现内存的非法访问系统错误,但这个错误是不会被编译器和链接器发现的,因而能够顺利地通过编译链接从而生成应用程序。
但在程序运行时却会出现错误,这个错误一般会被操作系统捕获,捕获后如果系统中没有实时的调试器则会提示关闭程序,如系统中有实时的调试器在则会由调试器接管应用程序。
实时调试器一般会提示用户选择直接退出程序或者启动调试,如下图则是在系统中安装有VC6所带实时调试管理器(DebugManager)时的情况:
当用户选择[确定]时将由VC启动调试器来调试程序,如下图所示:
对于需要调试的程序,使用程序本身并不需要的输出语句来进行跟踪,是调试程序的一个最重要的方法。
其中最常用的方法就是在程序的关键位置用printf语句或cout语句输出当前关键变量或某些变量的值,通过查看这些变量的值的变化来查找程序出错的原因。
当然,如果愿意,你可以在每行源代码的后面都加上输出语句,从而一步一步地追踪程序的行为,但这样作在时间和精力上是缺乏效率的。
因此必须经过认真思考和选择,确定应在何处插入输出语句之后,跟踪才可能凑效。
插入printf和cout输出语句的方法存在一个缺点,就是当程序调试完毕后我们必须将所有程序不需要的输出语句逐一手动删除,如果再次需要调试时又要经历逐一加上和删除的过程,很不方便。
为此,在C++中提供了另外的处理方法实现对变量的检查,并且能很方便地让这些检查语句失效,其中最常用的就是assert宏。
assert宏接收一个表达式,如果这个表达式为真,则无动作,否则中断当前程序执行(参考并完成课堂实践内容1)。
使用assert宏的方便之处在于,在调试完成之后,assert语句还是可以保留在程序中,但它们可以被很有效地“关闭”掉。
我们所要作的只是在“#include<
assert.h>
”语句前加上“#defineNDEBUG”即可。
如果你以后又需要进行调试,并想恢复原来的所有assert宏,只需删除这行宏定义语句即可。
与assert宏相对应的一个宏是VERIFY宏,所不同的是,在Release版本中ASSERT不计算输入的表达式的值,而VERIFY计算表达式的值(VERIFY宏是MFC中的宏)。
另一个常用的方便的语句是trace宏,它的使用方法和printf完全一致,能在output框中输出调试信息(参考并完成课堂实践内容2)。
三、交互式调试
使用集成开发环境所带的调试器来边运行程序边观察和调试是非常方便的。
这种调试方法一般首先要在程序中关键位置设置断点,然后开始调试程序。
此时可以让程序单步运行,也可以让程序直接运行到光标所在的那行,也可以让程序运行到断点处,然后在程序暂时停止时查看所有变量的值。
通过在程序的某行右击鼠标并选择[Insert/RemoveBreakpoint]就可以很方便地在此行处加上或去掉断点(位置断点)。
也可以通过把光标移动到需要设置断点的代码行上,然后按F9快捷键。
另一种方法是按快捷键CTRL+B或ALT+F9,或者通过菜单Edit/Breakpoints打开Breakpoints对话框,之后点击[Breakat]编辑框的右侧的箭头,选择合适的位置信息来设置。
一般情况下,直接选择linexxx就足够了,如果想设置不是当前位置的断点,可以选择Advanced,然后填写函数、行号和可执行文件信息。
如下图所示:
如果调试完程序要去掉断点,可以把光标移动到给定断点所在的行,再次按F9就可以取消断点。
也可以在打开Breakpoints对话框后,选择其中的[Remove]或[Removeall]来去掉某个或全部断点。
此外,在Breakpoints对话框可以设置的断点也分为条件断点、数据断点和消息断点三类。
我们可以为断点设置一个条件,这样的断点称为条件断点。
对于位置断点,可以通过单击Conditions按钮,为断点设置一个表达式。
当这个表达式发生改变时,程序就被中断。
值得一提的是最后一个设置,它可以让程序先执行多少次后才到达断点。
这种情况在循环中特别有效,因为我们常常希望让程序运行到一个临界值状态时才中止它。
数据断点则只能在Breakpoints对话框中设置。
选择“Data”页,就显示了设置数据断点的对话框。
在编辑框中输入一个表达式,当这个表达式的值发生变化时,数据断点就到达。
消息断点:
VC也支持对Windows消息进行截获。
他有两种方式进行截获:
窗口消息处理函数和特定消息中断。
在Breakpoints对话框中选择Messages页,就可以设置消息断点。
如果在Breakpoints对话框中写入消息处理函数的名字,那么每次消息被这个函数处理,断点就到达。
此外,在交互式调试中,我们还可很方便地在程序运行时“即时”地改变变量的值,甚至“即时”地改变寄存器或内存中的值。
例如你可以在Variable窗口中直接又击你想改变的变量值并键入你想使有的值再回车即可,当程序恢复运行时这个变量的值就是你输入的值而不是以前的值了(完成课堂实践内容3)。
四、C++的异常机制与标准异常处理
由于我们的程序在运行中可能出现异常,而在现代操作系统中一般都有对异常进行相应处理的机制,即当某些应用程序因错误导致如死锁、不响应或引起系统资源(如内存不足或CPU被占用)不足时操作系统会根据异常的类型作相应的处理。
因此,要更好地掌握调试技巧,有必要对异常处理机制有一定程度的了解。
//例一
#include<
io.h>
stdio.h>
stdlib.h>
voidmain(void)
{//throw2;
/*如果在try块外抛出异常则不会被本程序中的catch块捕获
而会被操作系统捕获并由操作系统来处理*/
try{
if((_access("
123.txt"
0))==-1)throw1;
}
catch(int)
{
printf("
File123.txtnotexists\n"
);
C++中的异常处理机制由try/throw/catch三个部分组成,一般将被监控的代码放在try块中,然后使用if语句来判断异常是否发生,发生则throw出异常(异常可以是int型对象、字符串型对象或某个类的对象),然后由catch块捕获抛出的异常并处理。
如下例:
一个未经捕获的异常是没有为其指定catch模块的异常,这样的异常将导致std:
:
terminate()函数被调用,它又通过调用std:
abort()来终止程序。
//例二
iostream>
usingnamespacestd;
{inti;
cout<
<
"
请输入错误号:
;
cin>
>
i;
try
{
switch(i)
{
case1:
throw1;
break;
case2:
throw2;
case3:
throw3;
case4:
throw4;
case5:
throw5;
case6:
throw6;
default:
throw"
不明错误"
}
catch(intj)
switch(j)
case1:
std:
cout<
"
捕获"
<
j<
号错误"
std:
endl;
case2:
catch(...){
cout<
捕获不能识别的错误"
程序结束。
如果异常的种类很多,为了区分这些异常,可以为它们指定一个标号,根据发出的错误信号来分别处理这些异常。
这种方法在要处理的异常种类太大时,是不合适的。
例如,如果所有的库都发出整数,catch模块将变成“拥挤且充满冲突的、各种异常处理代码堆积的地方”;
为此,C++标准定义了标准异常类,而程序员们则使用标准异常类的派生类来定义和区别异常的种类,以下是对C++中标准异常的介绍。
标准异常类
1、语言本身或标准程序库所抛出的所有异常,都派生自基类exception。
标准异常可分为三组:
①语言本身支持的异常;
②C++标准程序库发出的异常;
③程序作用域之外发出的异常。
2、语言本身所支持的异常
此类异常用以支撑某些语言特性,所以从某种角度来说它们不是标准程序库的一部分,而是核心语言的一部分。
如果以下操作失败,就会抛出这一类异常。
◆全局操作符new操作失败会抛出bad_alloc异常;
◆dynamic_cast操作失败会抛出bad_cast异常;
◆如果交给typeid的参数为零或空指针,将抛出bad_typeid异常;
◆如果发生非预期的异常,bad_exception将会被抛出,一般情况下这将导致unexpected()函数被执行,后者通常会唤起terminate()终止程序。
3、C++标准库发出的异常
C++标准程序库发出的异常总是派生自logic_error。
logic_error的定义:
namespacestd
classlogic_error:
publicexception
{public:
explicitlogic_error(conststring&
whatstring);
};
logic_error的抛出:
strings;
throwstd:
out_of_range(s);
此外,标准程序库的I/O部分提供了一个名为ios_base:
failure的特殊异常,当数据流由于错误或由于到达文件尾端而发生状态改变时,就可能抛出这个异常。
4、程序作用域之外的异常
派生自runtime_error的异常,用来指“不在程序范围内,且不容易回避”的异常。
◆range_error指出内部计算时发生区间错误
◆overflow_error指出算术运算时发生上溢错误
◆underflow_error指出算术运算时发生下溢错误
5、
/*例三本例首先创建一个支持MFC的win32
consoleapplication,然后在工程主源文件的前面加上#include<
stdexcept>
并去除
CStringstrHello;
strHello.LoadString(IDS_HELLO);
cout<
(LPCTSTR)strHello<
endl;
,
然后在对应位置加入下列代码*/
start:
try
throwout_of_range("
程序exception3的第37行throwout_of_range语句出现越界错误!
);
catch(out_of_rangeexception3)
switch(MessageBox(NULL,exception3.what(),"
出现错误"
MB_ABORTRETRYIGNORE|MB_ICONERROR))
caseIDABORT:
cout<
程序被强行终止!
return0;
caseIDRETRY:
gotostart;
caseIDIGNORE:
程序正常结束。
一般情况下,标准异常总是由运行时库函数、标准模板库或操作系统的API函数抛出的(并由操作系统来统一作处理的),但你也可以在自己的程序中抛出某些标准异常,这些异常只需要一个字符串参数,它将成为被what()返回的描述字符串。
这个程序首先弹出一个对话框提示出现错误,当选择[忽略]时程序输出“程序正常结束”,当选择[重试]时将不停弹出对话框,当选择[终止]时程序输出“程序被强行终止!
”。
(完成课堂实践内容4)
五、学习和提高调试技巧
intmain()
inthigh;
printf("
%d\n"
high);
调试是一个程序员最基本的技能,其重要性甚至超过学习一门语言。
不会调试的程序员就意味着他即使会一门语言,却不能编制出任何好的软件。
要想提高自己的程序调试技巧,首先要掌握常用的调试手段,可以预见,绝大部分调试都是用最常见的调试手段所解决的,只在很少的情况下才需要更高深的调试技巧和更特殊的方法。
其次要对常见错误有一定的了解,因为很多情况下都是这些常见的错误引起的程序异常,如下例是最常见的错误之一:
这个例子中展示了当没有正确地初始化变量时的结果,在程序运行之前你能否预期它的输出呢?
很显然这个程序不会出现任何编译、链接和运行时错误,但它确确实实与我们的想像不同,特别是在很长的程序中,变量的声明和变量的使用很可能相距很多行代码,甚至可能不在同一个源程序中,这就更增加了追踪此类错误的难度。
在掌握了基本的调试方法之后,我们才应该去掌握那些更高深的调试技巧,如学会查看程序反汇编代码、学会查看内存、调用堆栈和寄存器值等等。
课堂实践内容:
1、阅读MSDN中“MSDNLibrary-July2001/VisualToolandLanguages/VisualStudio6.0Documentation/VisualC++Documentation/usingVisualC++/VisualC++Programmer'
sGuide/Run-TimeLibraryReference/AlphabeticFunctionReference/AthroughB/assert”中的内容并完成其中的例子程序。
2、阅读MSDN中“MSDNLibrary-July2001/VisualToolandLanguages/VisualStudio6.0Documentation/VisualC++Documentation/usingVisualC++/VisualC++Programmer'
sGuide/Debugging/DebuggingTechniques,Problems,andSolutions/UsingMFCDebuggingSupport/DiagnosticFeatures/TheTRACEMacro”中的内容并完成其中的例子程序。
3、在上一实践内容的基础上为例子程序分别设置三类断点并启动调试器进行调试,并完成以下内容:
(1)查看watch窗口中各变量的值及变化过程;
(2)逐一打开[View/DebugWindows]中的子菜单,思考各有何用途;
(3)熟悉Debug工具条的打开和各按键的作用,了解调试器中Debug菜单下各子菜单的功能。
(4)尝试直接改变变量的值并继续开始调试,再观察运行结果。
4、分别编译和运行本小节的三个例子程序,思考C++的异常处理机制有何好处。
课后实践内容:
5、请认真阅读《C++标准程序库》(侯捷译华中科技大学出版社)一书中关于标准异常的相关内容。
6、请认真阅读本篇辅助材料《高质量C++-C编程指南》。