




已阅读5页,还剩58页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第23章有错也不怕 错误与调试 当程序没有按照程序员的预期执行或意外中断时 就产生了错误 调试就是查找错误发生点 错误原因的过程 程序错误是编程实践中常见的问题 既使最有经验的程序员也不能保证一次就编写出没有任何错误的程序 编写程序的大部分时间实际上就是在进行调试 本章将向读者详细讲解错误和错误的调试 通过本章的学习 读者可以识别程序中的错误 并学会调试和修改这些错误的能力 从而编写出高质量的代码 23 1什么是错误 程序错误常直接写做英文的bug 也称为缺陷 臭虫 它指在软件运行中因为程序本身有错误而造成的功能不正常 死机 数据丢失 非正常中断等现象 将程序错误称为bug是由于计算机发展早期的一件事情 二战期间 哈珀中尉带领他的小组制造了一台称为 马克二型 的计算机 一天 马克二型突然死机了 技术人员试了很多办法 最后定位到第70号继电器出错 哈珀观察这个出错的继电器 发现一只飞蛾躺在中间 已经被继电器打死 他小心地用镊子将蛾子夹出来 用透明胶布贴到 事件记录本 中 并注明 第一个发现虫子的实例 此后 计算机界就习惯将计算机的各种非正常事件都称为bug 按照错误的发生时间不同 可以将错误分成语法错误 链接错误和运行时错误 其中 语法错误发生在编写代码时 也叫编译时错误 链接错误发生在将程序代码链接为可执行程序的过程中 运行时错误则发生在运行程序的过程中 3种错误的排查和解决难度依次为语法错误 链接错误和运行时错误 有时 程序虽然没有发生上述3种错误 但是运行结果却不是预期的 这时就发生了逻辑错误 逻辑错误往往是算法设计错误造成的 本章主要讨论前3种错误类型 23 2错误的种类 为了处理错误 首先要搞清楚错误的内容和发生时机 这些错误大多发生在编写源代码 并将源代码转换成可执行程序的过程中 下面详细讲解几种常见的错误 23 2 1语法错误 语法错误发生在编写源代码时 指程序中包含了违反语法规则的语句 这类错误在编译时由编译程序自动发现 这类错误很普遍 是初学者最容易犯的错误 也很顽固 即使是最有经验的程序员也难以保证不犯这类错误 示例23 1 从命令行接受数据并输出 该程序在编译时会输出如下内容 分析 从输出可以看出 该示例共有4个错误 其中 第1个错误是第5行语句中的关键字end书写错误引起的 正确的写法应该是endl 第2个错误是第6行错用了 第3个错误就需要仔细理解了 从输出结果看出错误发生在第8行 错误内容中却说是data前缺少 号 这说明是第7行缺少 号 导致两行连在了一起 但是编译器发现这两行是两条独立的语句 所以提示8行有错 第4个错误是8行括号不全引起的 应该在 3 后补一个 所有程序员终生都要受语法错误的困扰 即使再小心仔细也难以避免 但是语法错误也是最容易处理和发现的 只要按照提示找到错误点 根据错误内容修改即可 这类错误只能随着经验的积累逐步的减少 却不能根除 有时编译器还会对某些语句给出警告 例如 将浮点数赋给整型数 将很小的数作为被除数等 这些不是错误 但却潜在地存在问题 也是需要高度重视的 23 2 2链接错误 链接错误发生在程序链接时 将编译过的程序与程序使用的库链接生成可执行程序时 如果不能在所有的库和目标文件内找到所引用的函数 变量或标识符 就会产生链接错误 这常常是符号不存在 拼写不正确或者使用错误引起的 示例23 2 链接错误的示例代码 该程序编译链接时 会有如下输出 分析 从输出可以看出示例代码除了仅有一个数据转换警告外 已经通过了编译 可以不管这个警告 下面可以看出有3个链接错误 第1个是提示函数fun 找不到 这是因为声明的fun 函数的参数为浮点型 但定义时却错写成了整型 所以链接程序认为找不到参数为浮点型的fun 函数 第2个错误是声明了外部变量x 但是却没有找到定义它的库文件 第3个是提示有两个无法找到的外部引用 所以导致不能链接为可执行程序 一般来讲 可以将链接错误分为工程内链接错误和工程外链接错误两种 其中 工程内链接错误指工程内使用的对象在链接时未能找到 是代码级别的 工程外链接错误指工程使用的外部对象未能找到 一般是使用外部编译好的库造成的 下面分别予以说明 1 工程内链接错误 这包括函数或变量不存在和函数或变量所在的文件没有被正确编译两种 其中 前者发生的原因是由于函数和变量只声明未定义 函数声明和定义的参数列表不一致 或者拼写错误等 后者是由于函数和变量所在的文件没有加到工程中 预处理宏或条件编译导致函数或变量没有被正确编译等 2 工程外链接错误 这包括链接的函数或变量没有被正确导入 找不到链接的库文件 调用方式错误等 注意 如果程序中用到了外部库或头文件 则需要在 tools options 的directories页增加路径 以避免链接错误 23 2 3运行时错误 当程序链接完后就生成了可执行文件 这时就可开始程序的执行 虽然已经排除了编译时错误和链接错误 但是在运行时仍然可能发生各种各样的错误 这种运行时发生的错误就叫运行时错误 它多是因为发生了计算机不能处理的事件 而导致程序不正常结束 有时也叫异常 示例23 3 下述代码存在指针相关的运行时错误 分析 该示例在运行时将会发生错误 这种错误在编译和链接时不会发现 指针p在释放后再次被使用 这导致了内存访问错误 此外 由于变量x要在运行时从命令行获得输入 所以语句 p p x 存在发生被除数为0的运行时错误 错误发生时的画面如图23 1所示 图23 1运行时错误 运行时错误是程序执行期间发生的错误 它不同于编译期间发生的错误 运行时错误可能是程序中的bug引起的 也可能程序并无错误 例如机器存储器不够引起的 运行时错误主要包括以下几类 硬件检测的错误 例如示例23 3中的内存错误和被0除错误 系统错误 例如文件操作失败错误 逻辑错误 例如数组越界错误 其他错误 例如输入数据格式错误 运行时错误经常会发生 但是一个好的软件应该尽可能避免这种错误 或者是做好从这种错误中恢复的预案 当发生这种错误时 程序往往会立即终止并退出 这就会导致申请的内存没有释放 打开的文件没有关闭等问题发生 运行时错误不像语法错误和链接错误那样 编译程序和链接程序会定位到具体的语句行上 但是开发环境一般都会提供带有单步执行的调试功能 可以方便地跟踪程序 查找出错的语句 23 3排查错误 错误的排查指发现并解决错误 错误的发现就是要找出错误的原因和发生错误的语句 由于常将错误称为bug 所以错误的发现也被称为debug 错误的解决是在发现错误之后 通过分析错误的原因 纠正错误的语句 错误的解决要依赖于实际的程序和程序员 本节只讲常见的错误发现方法 23 3 1看懂错误信息 当发生错误时 编译器会给出一些提示 根据这些提示就可以查找并定位到错误发生点 下面就来分析一下示例23 2的错误提示 最后一行显示 test23 2 exe 3error s 0warning s 表明有三个错误和零个警告 第一条错误的内容是 test23 2 obj errorLNK2001 unresolvedexternalsymbol int cdeclfun float fun YAHM Z 首先 test23 2 obj表明错误发生在test23 2 obj目标文件中 其后的errorLNK2001是错误号 通过该错误号可以查询具体的错误信息 后面给出了错误原因 这里是说 外部符号 int cdeclfun float 找不到 最后括号里的内容是该函数的入口点 一般不需要去管它 23 3 2错误发现的常见方法 有些错误从错误提示就可看清楚原因 这类错误多是编译 链接时的硬错误 对于程序中的逻辑错误 堆栈溢出 算法错误等 有时很难发现错误发生点 即使错误提示给出了错误的发生点 有时也并不是真正的位置和原因 这时就需要对程序进行调试来查找并解决问题 调试一般有以下3种方法 1 调试器 首先是使用专业的调试工具 如gdb dbx valgrind等专门的调试工具 其次是使用IDE集成的调试工具 这些工具都是通过设置断点来跟踪程序 当运行到断点处时 程序终止 然后就可以 单步 跳过 进入 等多种方式跟踪程序的执行 2 加输出语句 有些时候 使用调试器的调试过程比较慢 也较难看出直观的结果 这时可以考虑自行在程序内加入输出语句 将有疑问的变量值输出 通过与预期的结果比较 观察这些值的变化 从而定位错误发生点并找出错误 在VisualC 中 如果选择支持MFC 还可以使用TRACE宏来输出内容 该宏是将内容输出到debug窗口中 而且只有在debug状态下才能起作用 3 断言 用户还可以使用断言来判断某个值是否是0 有assert ASSERT VERIFY这3种 其中assert是标准C 中的宏 ASSERT和VERIFY是MFC中的宏 用被测试的变量或表达式作为它们的参数 如果参数为0就会弹出错误窗口 说明 从广义上来讲 程序错误并不表示程序一定就存在问题 只是说程序没有按程序员的预期去执行 例如 如果算法设计有误 或者程序逻辑错误 那么就程序本身来说没有任何问题 它忠实地按照代码的安排执行了 但是计算的结果却不是预期的 23 3 3如何调试 调试就是理解系统的行为并调整到需要的状态的过程 一般来讲 调试时需要遵循以下3个原则 调试的原则之一就是要理解系统的行为 这就是说 程序员要首先理解系统在干什么 而不是想当然地认为系统应该怎么样 如果不理解系统 就不能指望让系统完成预期的工作 因为任何一个改动都有可能影响到其他的方面 这样有可能解决了旧的bug 却带来了新的bug 调试的原则之二是对程序的任何改动都要小心谨慎 避免引入新的问题 对程序的任何改动都有引入新问题的危险性 如果解决了一个问题 又引入了一个或多个新的问题 那么就得不偿失 但是这是难以避免的 只要加入新的未经测试过的代码就有可能导致新的问题存在 基于这个原因 也建议程序员在编程时尽量采用封装技术 这样当被封装的对象测试无误后 就可以重复利用 而不必担心程序的调试会影响到对象内部 原则之三是要保持版本的跟踪 即每次改动都最好保留一份备份版本 因为 并不能保证每次的程序改动都是正确无误的 有时改动后新引入的问题比原来的问题更棘手 更难解决 如果有一份先前的版本就可以还原 或者说撤销本次的改动 这样就可以避免花费不必要的精力去解决新的问题 23 4常见bug的分类 语法错误比较容易识别 这一节主要介绍一下其他的非语法类常见错误 1 内存泄露 内存泄露指分配的内存在用完后没有收回 导致一段时间后内存减少 系统变慢 例如 malloc new等动态申请内存的操作 当申请了一块内存然后在释放前又一次申请时 那么原来那块内存将丢失不能被收回 2 逻辑错误 当语法正确 但是却没有达到预期的目的时 就发生了逻辑错误 这多是算法设计有缺陷或代码输入错误造成的 这种错误无法由编译器和调试器发现 因为程序没有问题 并如实按照要求去执行了 3 内存越界 当指针或数组使用了不属于自己的内存时 就会引发该错误 当这种错误仅导致访问到了不该访问的数据时 未必会引起程序中断 但是当访问到系统区域时 则多数情况下会导致程序崩溃 4 无限循环 对于循环类语句 如for while等 如果循环终止条件设置不恰当 就会导致程序无限循环下去 与第2种错误一样 程序也没有问题 但是执行时却不会停止 5 指针错误 指针导致的错误主要有3类 没有初始化 却使用 已删除 但仍然使用 指针无效 前2类都可以通过观察源代码来排查 第3类则必须通过分析来找出原因 例如 强行将字符型指针转换为整型指针 系统只是对内存块的边界作了重新界定 但是对于数据没有任何操作 由于内存块扩大了 转换后指针的数据将是没有任何意义且未知的 6 编码错误 这主要指声明与定义不一致 或者使用与定义不一致 例如 声明某个函数接收3个参数 但是定义时却只有2个 或者使用时只给出了2个等 具体的编程实践中会出现各种各样的错误 上面仅列出了常见的几类 23 5调试的窍门 相关研究表明软件的编写中 大多数时间和精力是花在了调试上 好的调试方法也是编写好程序的关键 本节将介绍常用的几种调试技巧 主要有断言 轨迹 断点等 23 5 1使用断言assert 断言就是判断 assert断言有两种 分别是assert和ASSERT 其中 前者是标准C 中的宏 后者是MFC中的宏 断言的功能是测试它的参数 若参数为0 则中断执行并打印一段说明消息 在Release版本的程序中它不起任何作用 assert和ASSERT的使用方法一样 下面以assert断言来说明断言的使用方法 示例23 4 演示assert断言使用的方法 分析 当从命令行输入2时 导致assert的参数为0 将会显示图23 2所示的界面 图23 2assert断言的输出 说明 ASSERT宏同assert使用方法一样 但需要包含afx h头文件 afx h是MFC的头文件 后面章节中的VERIFY和TRACE宏都需要MFC的支持 23 5 2使用断言verify verify也是一种断言 不仅可以断言简单变量 还可以断言含有函数的表达式 它是MFC中的宏 使用时写做VERIFY 示例23 5 演示VERIFY断言的使用方法 分析 当从命令行输入0时 导致VERIFY的参数为0 将会显示图23 3所示的画面 图23 3VERIFY断言的输出 提示 LINK fatalerrorLNK1104 cannotopenfile nafxcwd lib 编译时出现此问题 说明将项目设置设为了 在静态库中使用MFC MicrosoftVisualC 6 0标准版不支持静态链接到MFC库 解决方法为使用动态链接到MFC库 具体操作为 1 打开您的 Project 2 从 Project 菜单中单击 Settings 3 在 Settingfor 选择 Allconfigurations 4 单击 General选项卡 如果它是不可见的使用选项卡滚动按钮向左滚动 5 在 Microsoftfoundationclasses 组合框选择 UseMFCinaSharedDLL 6 单击 Ok 以保存所做的更改 23 5 3assertVSverify ASSERT与VERIFY宏在Debug模式下作用基本一致 二者都对表达式的值进行计算 如果值为非0 则什么事也不做 如果值为0 则输出诊断信息 但是在Release模式下效果完全不一样 ASSERT不计算表达式的值 也不会输出诊断信息 VERIFY计算表达式的值 但不管值为0还是非0都不会输出诊断信息 VERIFY与ASSERT用在程序调试上并无本质上的区别 但是建议尽量使用ASSERT宏 因为VERIFY在release版本中虽然不输出 但仍然要做计算 这会浪费不必要的CPU时间 23 5 4轨迹跟踪 轨迹跟踪就是人为地加入输出语句 可以是输出变量的值 也可以是输出特定的记号 经过这样处理的程序在运行时会输出一系列的轨迹 通过观察这些输出就可以分析出程序运行了哪些语句 在哪里出了问题 这种方法有时比使用调试器要轻便和快捷许多 示例23 6 在示例23 3中加入输出语句 用轨迹法跟踪语句的执行 运行后的输出如图23 4所示 图23 4轨迹跟踪的输出 分析 输出中带 的数字是程序中加入的输出语句 不带 的是命令行输入的语句 这些带 号数字的输出勾画出了程序的运行轨迹 用户从这些轨迹可以看出程序运行到哪里了 以及运行了那些语句 从图23 4中可以看出 在输出 7 后 程序发生运行时错误 而预期的 8 没有输出 这表明错误发生在这两条输出语句中间 这样就很容易地定位到了语句 p 12 上 该语句出错是因为使用了已经销毁的指针造成的 如果创建工程时选择了MFC支持 还可以使用TRACE宏 该宏与上述方法的区别在于它是将输出内容送到debug窗口中 而且只有在debug状态下才起作用 启动debug状态的方法是 依次选择菜单 Build 菜单项 startdebug go 即可 工程创建提示 选择win32ConsoleApplication 按下 OK 按钮 然后选择AnapplicationthatsupportsMFC 打开test23 7 cpp添加相应代码 示例23 7 使用TRACE宏来跟踪语句的执行 分析 该示例运行时 将会在debug窗口内看到TRACE的输出 这种轨迹跟踪方法的好处是它只在debug状态下才起作用 因此在release版本中不需要将这些语句删除 而前一种跟踪方法则需要删除输出语句 23 5 5使用断点 断点是调试器设置的一个代码位置 当程序运行到断点时 程序中断执行 回到调试器 断点是最常用的技巧 调试时 只有设置了断点并使程序回到调试器 才能对程序进行在线调试 断点有条件断点 数据断点 消息断点3种 条件断点就是带有条件判断的断点 只有当满足该条件时 断点才能中断 否则会继续执行下一条语句 数据断点是对某个表达式进行监视 当表达式的值发生改变时 就发生中断 进入跟踪状态 一般情况下 这个表达式应该由运算符和全局变量构成 消息断点VC也支持对Windows消息进行截获 即当某个特定消息发生时产生中断 进入跟踪状态 23 6使用交互式调试 VisualC 编程环境给程序员提供了方便且强大的调试环境 在该环境下 可以设置断点 观察变量的内容 跟踪程序的执行等 本节就向读者详细讲解它的调试功能 23 6 1设置和删除断点 设置断点时 首先把光标移动到需要设置断点的代码行上 然后通过下述两种方法来设置 按F9键直接设置断点 选择Edit Breakpoints命令 打开Breakpoints对话框 单击Breakat编辑框的右侧箭头 选择合适的位置信息 下面是在Breakpoints 断点 对话框中设置三类断点的方法 1 条件断点 在location页 当选择好断点位置后 单击condition按钮 在弹出的对话框中输入断点条件 例如 输入 x10 则当x的值为负数或大于10时将发生中断 2 数据断点 首先设置普通断点 然后进行调试 当程序停在预设的断点处时 在Breakpoints对话框中的Data页内输入被监视的变量名 然后按F5键继续运行 当该变量的值发生改变时程序就在此中断 进入跟踪态 注意 数据断点只能在Breakpoints对话框中设置 选择Data页 就显示了设置数据断点的对话框 在文本框中输入一个表达式 当这个表达式的值发生变化时 数据断点就到达 一般情况下 这个表达式应该由运算符和全局变量构成 3 消息断点 VisualC 也支持对Windows消息进行截获 只能在Breakpoints对话框中设置 选择Message页 选择要截获的消息以及消息发生的函数 当在该函数内收到该消息时 就会中断 去掉断点时 只需把光标移动到给定断点所在的行 再次按下F9键就可以取消断点 也可以打开Breakpoints对话框后 按照界面提示去掉断点 23 6 2使用Debug窗口 当进入调试状态时 有以下几个辅助工具可供使用 变量观察面板 Watch 在观察窗口中 加入特定的变量 就可以监控它的取值变化 如图23 5就是在Watch面板内观察变量szName1的画面 图23 5Watch对话框 快速查看对话框 QuickWatch 功能和观察面板差不多 当输入变量时 立刻就会显示变量的内容 如图23 6所示为QuickWatch对话框 图23 6QuickWatch对话框 变量面板 Variables 变量面板有3个标签 如图23 7所示 Auto标签显示了当前语句和前一条语句用到的变量 Locals标签显示当前函数的局部变量 this标签显示了this指针执行的对象 图23 7Variables面板 寄存器面板 Register 该面板能够监控CPU的寄存器 标志值连同浮点堆栈 如图23 8所示 图23 8Register面板 内存面板 Memory 该面板可显示从某特定地址开始的虚拟内存 如图23 9所示 用户可以通过Address文本框指定虚拟内存的开始地址 图23 9Memory面板 调用栈面板 CallStack 该面板显示当前程序执行的一系列函数调用 当前函数在堆栈的顶端 如图23 10所示为CallStack面板 图23 10CallStack面板 反汇编面板 Disassembly 该面板提供了查看编译器生成的对应于源代码的汇编指令的功能 23 6 3使用Watch面板 在23 6 2节的7种工
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 钣金安全考试题及答案
- 安全技术试题及答案
- 安全管护培训试题及答案
- 不良资产处置行业创新模式与市场拓展路径研究报告
- 便利店智能支付与无感购物体验研究报告(2025年)
- 门店运营课程培训课件
- 中国南方地区课件
- 中国单一制课件
- 护理文书书写规范
- 原发性肝癌护理课件
- 2023年晋江市医院医护人员招聘笔试题库及答案解析
- 结构设计总说明(带图完整版)分解
- 第二外语(日语)试卷
- 食品营养标签的解读课件
- 二手新能源汽车充电安全承诺书
- 品质异常8D报告 (错误模板及错误说明)指导培训
- 公共关系学-实训项目1:公关三要素分析
- 网页设计基础ppt课件(完整版)
- 贵阳市建设工程消防整改验收申请表
- 2021-2022学年云南省昆明市高一下册物理期末调研试题(含答案)
- 吉安土地利用总体规划
评论
0/150
提交评论