什么是调试器? 它如何帮助我诊断问题?

这是一个通用问题,用于帮助那些程序出现问题但不知道如何使用调试器诊断问题原因的新程序员。

这个问题包括三类更具体的问题:

  • 当我运行我的程序时,它不会产生我期望的输入输出。
  • 当我运行我的程序时,它崩溃了,给我一个堆栈跟踪。我有 检查了堆栈痕迹,但我仍然不知道问题的原因,因为堆栈跟踪没有为我提供足够的信息。
  • 当我运行我的程序时,它会因为一个内存区段错误(SEGV)而崩溃。
20297 次浏览

调试器是一个可以在程序运行时检查程序状态的程序。技术手段,它使用这样做对于理解使用调试器的基本知识是不必要的。当程序到达代码中的特定位置时,可以使用调试器暂停程序的执行,然后检查程序中变量的值。可以使用调试器非常缓慢地运行程序,一次运行一行代码(称为 一步一个脚印) ,同时检查其变量的值。

使用调试器是一项必备的基本技能

调试器是帮助诊断程序问题的非常强大的工具。并且调试器可用于所有实用编程语言。因此,能够使用调试器被认为是任何专业或狂热的程序员的 基本技能。使用调试器 你自己被认为是 基本工作,你应该做 你自己之前,请求他人的帮助。由于这个网站是为专业和热心的程序员,而不是一个帮助台或指导网站,如果你有一个问题与一个特定的程序的问题,但没有使用调试器,你的问题很可能被关闭和投票否决。如果你坚持问这样的问题,你最终将被阻止发布更多。

调试器如何帮助您

通过使用调试器,您可以发现变量是否具有错误的值,以及在程序中哪里将其值更改为错误的值。

使用单步执行还可以发现控制流是否如您所期望的那样。例如,if分支是否在您期望的时候执行。

使用调试器的一般注意事项

使用调试器的细节取决于调试器,在较小程度上取决于所使用的编程语言。

  • 可以将调试器 接上到已经运行程序的进程。如果你的程序卡住了,你可以这样做。

  • 实际上,从一开始就在调试器的控制下运行程序通常更容易。

  • 通过指示程序应该停止执行的源代码文件和行号,或者通过指示程序应该停止的方法/函数的名称(如果希望在执行进入方法后立即停止) ,可以指示程序应该停止执行。技术意味着调试器用来使程序停止的过程称为 断点,而这个过程称为 设置断点

  • 现代调试器是 IDE 的一部分,并提供了一个方便的图形用户界面检查源代码和变量的程序,与点击界面设置断点,运行您的程序,并单步执行它。

  • 除非程序可执行文件或字节码文件包括调试 符号资料和对源代码的交叉引用,否则使用调试器会非常困难。您可能必须使用 编译(或重新编译)您的程序略有不同来确保信息的存在。如果编译器执行大量优化,那些交叉引用可能会变得混乱。因此,您可能必须使用 在关闭优化的情况下重新编译程序

我想补充的是,调试器并不总是完美的解决方案,也不应该总是调试的首选解决方案。下面是一些调试器可能无法为您工作的情况:

  • 程序中失败的部分非常大(也许是模块化差?)你不确定从哪里开始逐步执行代码。一步步走过去可能太费时间了。
  • 您的程序使用了大量回调和其他非线性流控制方法,这使得调试器在单步执行时感到困惑。
  • 您的程序是多线程的。或者更糟糕的是,您的问题是由竞态条件引起的。
  • 在错误发生之前,包含错误的代码会运行很多次。这在主回路中尤其成问题,在物理引擎中更糟糕,因为这个问题可能是数值问题。在这种情况下,即使设置断点,也只会让您多次触发它,而不会出现 bug。
  • 您的程序必须实时运行。对于连接到网络的程序来说,这是一个大问题。如果您在网络代码中设置了一个断点,那么另一端不会等待您逐步通过,它只会超时。依赖于系统时钟的程序,例如带有框架跳转的游戏,也没有好到哪里去。
  • 您的程序执行某种形式的破坏性操作,如写入文件或发送电子邮件,并且您希望限制需要运行它的次数。
  • 您可以知道错误是由到达函数 X 的不正确值引起的,但是您不知道这些值是从哪里来的。必须一遍又一遍地运行程序,将断点设置得越来越远,这可能是一个巨大的麻烦。特别是如果在整个程序中从许多地方调用函数 X。

在所有这些情况下,要么让您的程序突然停止可能会导致最终结果不同,要么手动逐行搜索引起 bug 的那一行太麻烦了。无论 bug 是错误的行为还是崩溃,这种情况都可能发生。例如,如果内存损坏导致崩溃,那么当崩溃发生时,距离最初发生内存损坏的地方太远了,没有留下任何有用的信息。

那么,还有别的选择吗?

最简单的就是记录和断言。在不同的位置向程序添加日志,并比较所获得的内容和所期望的内容。例如,看看您认为存在 bug 的函数是否一开始就被调用了。看看方法开始处的变量是不是你想的那样。与断点不同,存在许多没有发生特殊情况的日志行是可以接受的。您可以随后在日志中进行简单的搜索。一旦你碰到一条与你期望的不同的日志线,在同一区域添加更多。把范围缩小得越来越小,直到能够记录窃听区域的每一条线。

断言可以用来在错误值出现时捕获它们,而不是一旦它们产生了终端用户可见的效果。您越快捕捉到一个不正确的值,就越接近产生该值的行。

重构和单元测试。如果您的程序太大,那么一次测试一个类或一个函数可能是值得的。给它输入,然后查看输出,看看哪些不是您所期望的。能够将一个 bug 从整个程序缩小到单个函数可以在调试时间上产生巨大的差异。

在出现内存泄漏或内存跳转的情况下,使用能够在运行时分析和检测这些问题的适当工具。能够检测到实际腐败发生的地方是第一步。在此之后,您可以使用日志返回到引入错误值的位置。

记住,调试是一个倒退的过程。你得到了最终的结果——一个错误——并找到了它之前的原因。它是关于向后工作的,不幸的是,调试器只能向前进行调试。这就是良好的日志记录和事后分析可以提供更好结果的地方。