当前位置:嗨网首页>书籍在线阅读

02-调试简介

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

17.1 调试简介

“如果调试是消除bug的过程,那么编程必然是引入bug的过程。”

——E. W. Dijkstra

你可能会遇到这样的情况:程序不能正常工作,却无法找到原因。为了在代码中修复这类神秘的问题,你还添加了几个print语句并启用了跟踪日志记录。但上述操作似乎并没有起多大作用。不用担心,你并不孤单——每个程序员都有过类似的经历,并且花费了数小时才找到一个令人沮丧的错误,但它给实际的生产活动带来了破坏性的影响。

软件中的错误和偏差被称为 bug,移除它们的行为被称为调试。调试是一种检查软件故障前因后果的受控的、系统的方法。对任何有兴趣深入了解程序行为和运作方式的人来说,这是一项必不可少的技能。但是如果没有合适的工具,调试并不是一件很容易的事,开发者可能会忘记实际的bug,甚至可能在错误的地方查找bug。我们用来识别软件缺陷的方法会极大地影响它们继续正常运行所花费的时间。根据bug的复杂程度,通常会采用以下方法之一进行调试。

  • 输出行调试 :在这种方法中,我们将print语句安插到代码中所需的位置,以便考察这些可能会修改应用程序状态的地方,并在程序运行时监控输出结果。这种方式很简单,并且经常能够起作用,但它并不是万能的。这种技术不需要额外的工具,每个人都知道该怎么做,但它实际上是调试大多数错误的起点。为了辅助输出行代码调试,Rust提供了我们之前多次提及的Debug特征,以及dbg!、println!及eprintln!等一系列宏。
  • 基于读取—求值—输出—循环的调试 :解释性的语言(如Python)通常都有自己的解释器。解释器为你提供了一个读取—求值—输出—循环(Read-Eval-Print-Loop,REPL)的接口,你可以在交互式会话中加载程序并逐步检查变量的状态。这在调试程序时非常有用,特别是当你已经适当地对代码进行模块化,以便它可以作为函数独立调用时。
  • 不幸的是,Rust没有正式的REPL,它的整体设计并不真正支持这一特性。但是,miri项目的出现让情况有所改观。
  • 调试器 :通过这种方法,我们可在生成的二进制文件中使用特殊的调试符号编译程序,并使用外部程序监视它的执行。这些外部程序被称为调试器,最常见的调试器是GDB和LLDB。目前,它们代表最强大、最有效的调试方法,允许用户在程序运行时监控与之有关的大量信息。调试器使用户能够暂停正在运行的程序,并检查其在内存中的状态,以找出引入bug的特定代码。

前两种方法都比较简单,因此我们不需要在这里介绍它们,此处讲解第三种方法:调试器。调试器作为一种工具使用起来非常简单,但它们并不是那么容易理解,且编程新手并没有对它引起足够的重视。在后文节中,我们将使用GDB逐步调试Rust编写的程序,但在此之前,让我们先了解一些调试器的基础知识。