提问人:Raedwald 提问时间:8/19/2014 最后编辑:Peter MortensenRaedwald 更新时间:8/6/2023 访问量:24672
什么是调试器,它如何帮助我诊断问题?
What is a debugger and how can it help me diagnose problems?
问:
这是一个通用问题,旨在帮助程序有问题但不知道如何使用调试器来诊断问题原因的新程序员。
本题涵盖三类更具体的问题:
答:
调试器是一个程序,可以在程序运行时检查程序的状态。它用于执行此操作的技术方法对于理解使用调试器的基础知识不是必需的。当程序到达代码中的特定位置时,可以使用调试器停止程序的执行,然后检查程序中变量的值。可以使用调试器非常缓慢地运行程序,一次运行一行代码(称为单步执行),同时检查其变量的值。
使用调试器是一项预期的基本技能
调试器是一个非常强大的工具,用于帮助诊断程序问题。调试器可用于所有实用的编程语言。因此,能够使用调试器被认为是任何专业或发烧友程序员的基本技能。自己使用调试器被认为是在向他人寻求帮助之前应该自己完成的基本工作。由于此站点是为专业和爱好者程序员准备的,而不是帮助台或指导站点,因此如果您对特定程序的问题有疑问,但尚未使用调试器,则您的问题很可能会被关闭并投反对票。如果你坚持这样的问题,你最终将被阻止发布更多内容。
调试器如何帮助你
通过使用调试器,可以发现变量是否具有错误的值,以及其值在程序中的位置更改为错误的值。
使用单步执行,您还可以发现控制流是否符合预期。例如,分支是否按预期执行。if
有关使用调试器的一般说明
使用调试器的具体细节取决于调试器,在较小程度上取决于所使用的编程语言。
可以将调试器附加到已在运行程序的进程。如果您的程序卡住了,您可能会这样做。
在实践中,从一开始就在调试器的控制下运行程序通常更容易。
通过指示应停止执行的源代码文件和行号,或指示程序应停止的方法/函数的名称(如果要在执行进入方法后立即停止),可以指示程序应停止执行的位置。调试器用于使程序停止的技术方法称为断点,此过程称为设置断点。
大多数现代调试器都是 IDE 的一部分,它为您提供了一个方便的 GUI,用于检查程序的源代码和变量,并具有用于设置断点、运行程序和单步执行的点击式界面。
使用调试器可能非常困难,除非程序可执行文件或字节码文件包含调试符号信息和对源代码的交叉引用。您可能需要以略微不同的方式编译(或重新编译)程序,以确保信息存在。如果编译器执行大量优化,这些交叉引用可能会变得混乱。因此,您可能必须在关闭优化的情况下重新编译程序。
评论
我想补充一点,调试器并不总是完美的解决方案,也不应该总是调试的首选解决方案。下面是调试器可能不适合你的几种情况:
- 程序中失败的部分非常大(也许是模块化程度很差?),而且您不确定从哪里开始单步执行代码。逐步完成所有这些操作可能太耗时了。
- 您的程序使用了很多回调和其他非线性流控制方法,这使得调试器在单步执行时感到困惑。
- 您的程序是多线程的。或者更糟糕的是,您的问题是由竞争条件引起的。
- 包含该 bug 的代码在出现 bug 之前会运行多次。这在主循环中可能特别成问题,或者更糟糕的是,在物理引擎中,问题可能是数字问题。在这种情况下,即使设置断点,也只是让你多次点击它,而错误不会出现。
- 程序必须实时运行。对于连接到网络的程序来说,这是一个大问题。如果你在网络代码中设置了一个断点,另一端不会等待你单步执行,它只是会超时。依赖系统时钟的程序,例如带有跳帧的游戏,也好不到哪里去。
- 您的程序执行某种形式的破坏性操作,例如写入文件或发送电子邮件,并且您希望限制运行它所需的次数。
- 你可以说你的错误是由到达函数 X 的错误值引起的,但你不知道这些值是从哪里来的。必须一遍又一遍地运行程序,将断点设置得越来越远,这可能是一个巨大的麻烦。特别是当函数 X 在整个程序中的许多位置被调用时。
在所有这些情况下,要么让你的程序突然停止,要么导致最终结果不同,要么手动单步执行以寻找导致错误的一行,这太麻烦了。无论您的错误是不正确的行为还是崩溃,这同样会发生。例如,如果内存损坏导致崩溃,则在崩溃发生时,它离内存损坏最初发生的位置太远,并且不会留下任何有用的信息。
那么,有哪些替代方案呢?
最简单的就是日志记录和断言。在不同时间点将日志添加到程序中,并将您获得的内容与预期内容进行比较。例如,查看您认为存在错误的函数是否首先被调用。查看方法开头的变量是否是您认为的变量。与断点不同,可以有许多日志行,其中没有发生任何特殊情况。之后,您可以简单地搜索日志。一旦你击中了与你期望的日志线不同的日志线,就在同一区域添加更多。将范围缩小到越来越远,直到它足够小,能够记录错误区域中的每一行。
断言可用于在错误值发生时捕获错误值,而不是在它们对最终用户可见时捕获它们。您越快捕获不正确的值,您就越接近产生该值的行。
重构和单元测试。如果您的程序太大,则一次测试一个类或一个函数可能是值得的。给它输入,看看输出,看看哪些不是你所期望的。能够将 bug 从整个程序缩小到单个函数可以对调试时间产生巨大影响。
如果发生内存泄漏或内存踩踏,请使用能够在运行时分析和检测这些情况的适当工具。能够检测实际损坏发生的位置是第一步。在此之后,您可以使用日志返回到引入错误值的位置。
请记住,调试是一个向后的过程。你有最终结果 - 一个错误 - 并找到之前的原因。这是关于向后工作,不幸的是,调试器只能向前迈进。这就是良好的日志记录和事后分析可以为您提供更好结果的地方。
评论
The code that has the bug in it runs many times before it bugs out
这很容易解决。所有好的调试器都能够执行条件断点。例如,在 MSVC 中,可以将其设置为在多次命中断点后中断,或者设置为特定条件(如str == "abc" && i > 40000
评论