Tips & Tricks
调试 C++ 的七大技巧
今天我们邀请到 Greg Law。在这篇客座博文中,Greg 将分享一些调试 C++ 代码的技巧。
Greg (@gregthelaw) 是 Undo 的联合创始人兼首席执行官。他心中还是程序员,但喜欢同时参与软件世界和商界。Greg 认为将创新的软件技术转化为真正的商业发展特别有意义。Greg 在学术界和创新的初创软件公司都有超过 20 年的经验。
|
Brian Kernighan 有句名言:“每个人都知道,调试代码比编写代码要困难两倍。因此,如果您写代码已经非常巧妙了,怎么能去调试它呢?”对我来说,这并不仅仅意味着“保持简单”。它还意味着调试是编程的核心 – 如果您不善于调试,就不能成为一位出色的程序员。希望我最喜欢的 C++ 调试技巧能够让您在调试代码时像在编写代码时一样聪明和高效。
#1 拥有一整套调试工具
在其他条件相同的情况下,一个装备较好的开发者会比装备较差的开发者更快地避免和解决缺陷。以下是我建议每位程序员都应该知道如何和何时使用的 4 类调试工具。我还包括了商业和免费开源选项,供您探索:
类别
|
它能告诉您什么
|
示例工具
|
交互式调试器
|
暂停执行,探索“我的程序在做什么?”
|
GDB、strace
|
时间旅行调试器
|
在执行时间中前后移动,看看您的程序是如何达到其当前状态的。
|
UDB、rr、WinDbg
|
动态代码检查器
|
分析或测试您的代码,检查缓冲区溢出和其他缺陷。
|
Valgrind、ASan
|
静态代码检查器
|
分析您的代码以确定是否有发生特定缺陷的风险。
|
Clang Analyzer 和 Clang-Tidy、Coverity、Cppcheck、IDE 内置 linter
|
在这个 ACCU 的讲座中,Coverity 的 Dewang Li 和我一起浏览了这些类别的工具,并解释了它们的幕后工作原理。
来自 Anastasia 的 CLion 调试器提示:CLion 不仅让您用 GDB 或 LLDB 后端调试您的代码,它还可以帮助您使用其他工具,包括 Valgrind 和 Sanitizers 集成,以及各种静态代码分析选项。
#2 条件断点
断点让您可以在代码中的特定行或函数处停止程序的执行。一旦您的程序命中断点,它就会等待您的指示,检查或操作应用程序状态、恢复执行等。
为了帮助更有效地进行调试,我喜欢使用条件断点。我可以为断点定义一个条件,在满足条件的情况下停止执行,而不是在每次到达断点时都暂停(如果断点在一个循环中定义,这可能很乏味)。例如,如果变量“i”通常等于零,也许我想在“i”不为零时中断:
(gdb) break my_func if i!=0
您可以用您要调试的程序的编程语言编写几乎任何条件,这使得条件断点非常强大和有效。条件可以包括函数调用、变量的值,或者任何 GDB 表达式的结果。
要了解详情,请观看此视频教程。我在其中解释了条件断点和各种其他断点类型,以及如何在您的调试中使用它们。
来自 Anastasia 的 CLion 调试器提示:在 CLion 中,您可以轻松指定一个在每次断点被命中时进行检查的条件。
#3 观察点
和断点一样,观察点也会停止执行,但只要表达式的值发生变化就会停止执行,而不需要预测可能发生这种情况的特定代码行。观察点在调试并发问题时非常有帮助,比如在试图了解哪个线程或进程在改变共享资源时。表达式既可以像单个变量的值一样简单,也可以像运算符组合的多个变量一样复杂。示例包括:
-
对单个变量值的引用。
-
到适当数据类型的地址转换。例如,
*(int *)0x12345678
将在指定的地址监视一个 4 字节的空间(假设 int 占用 4 个字节)。 -
任意复杂的表达式,如
a*b+c/d
。该表达式可以使用程序的原生语言中有效的任何运算符(查看“语言”)。
此博文和视频演示了如何使用不同类型的观察点。
来自 Anastasia 的 CLion 调试器提示:如果您想知道如何在 CLion 中添加观察点,请参阅 Web 帮助。
#4 Python 中用户定义的调试命令
我建议根据您的项目和团队定制调试器。在 GDB 中,这可以通过在 Python 中创建用户定义的命令来实现。您可以做各种巧妙的事情来帮助检测(和解决)棘手的错误,让相关工作变得轻而易举。此外,还有很多其他技巧,您可以根据您的特定项目和调试需求来定制 GDB。
您以后可能会后悔没有充分利用 Python – 错失了提高调试速度的机会,更不用说您的总体生活质量了!这是一项小的时间投资,很快就能得到回报,而且随着时间的推移,回报率很高。
例如,您可以自动执行一项任务,例如将调试器输出签入源代码控制,并与您的队友共享。在这篇博文中,我展示了如何利用 Python 与 GDB 的集成来做到这一点。
来自 Anastasia 的 CLion 调试器提示:您知道在调试会话中,您可以直接从 CLion 访问 GDB/LLDB 控制台吗?选项卡显示了调试器的输出/错误流,并让您运行 GDB/LLDB 命令。
#5 Pretty-print 结构
打印变量、结构和类是调试的一个重要部分。默认情况下,调试器可能不会以一种让开发者容易理解的方式打印数值。
例如,当我打印
siginfo_t
结构时,打印命令会返回该结构中的所有数据,包括扩展它所使用的联合:乱糟糟的,不容易读懂!幸好,可以使用 “pretty-printer” 函数扩展 GDB。当 GDB 打印一个值时,它会检查是否有一个为该值注册的 pretty-printer。如果有,GDB 将使用它来显示这个值。否则,该值将以常规方式打印。
创建 pretty-printer 函数需要一些编码,但我保证它将为您节省大量盯着电脑屏幕的时间。我在这段视频中演示了在 GDB 中输出 pretty-print 结构的快速方式。
来自 Anastasia 的 CLion 调试器提示:如果您在 GDB 中添加了自定义 pretty-printer,CLion 将默认使用它们。
#6 时间旅行调试
很多时候,您需要知道您的程序实际做了什么,而不是您期望它做什么。这就是为什么调试通常包括多次重现错误,慢慢地找出越来越多的信息,直到确定。
时间旅行调试则消除了所有的猜测和试错 – 调试器可以直接告诉您刚刚发生了什么。
像 GDB 这样的免费调试器具有内置的时间旅行调试功能。它的效果不错,但您必须准备好牺牲(很多)性能。商业的专用时间旅行调试器,如 UDB,提供更快的时间旅行调试性能。
这个过程就像普通的调试,但您可以回溯时间。断点和观察点的工作方式是相反的,也很有帮助,例如,直接继续到程序中某一特定变量发生变化的上一个点。反向观察点的功能非常强大。我知道几个例子,对于一些在几个月甚至几年的时间里开发者都没有发现的错误,借助反向观察点在几个小时内就被解决了。
在这个视频中,我演示了 GDB 中的反向调试。欢迎观看:它逐步(以反向方式)向您展示了如何在我的程序中追踪一个错误。
#7 查找字节序列的 find 命令
有时当您调试时,您需要在程序的内存空间中找到一个特定的字节序列。也许您想查看指向一个特定对象的所有指针。所以,内存中每八个字节对应的字节序列就是一个您想要识别的地址。
GDB 中的
find
命令为您提供了一种不同的程序检查方式。所有搜索值都以程序的编程语言进行解释。例如,hello.c 的源语言是 C/C++;所以如果我们搜索字符串“Hello, world!”,它包括尾随的 '\0'
。GDB 还提供了关于程序进程的内存映射的信息,这可以帮助您在程序的某些内存段集中搜索。每一个找到的匹配地址都会被返回,同时也会有一个匹配数量的计数。
想要更多的 C++ 调试技巧?
我已经分享了七个提高 C++ 调试效率的技巧。希望这些技巧能对您有所帮助 – 有时对您平时的调试习惯和程序做一些小小的调整就能带来很大的改变。让我们一起分享吧!我也很想听听您的技巧。您可以在 Twitter 上联系我,或者在 LinkedIn 上找到我。
下面有一些资源可以帮助您在调试的道路上更顺利:
- GDBWatchpoint – 订阅我在 undo.io 上的博客
- 获取 GDB – 免费的 C/C++ 调试器
- UDB – 商业版的时间旅行调试器(30天免费试用)
而从 JetBrains 方面,这里还有一些关于 CLion 的资源:
谢谢您,Greg,感谢您分享这些实用的技巧!
还有一个问题要问我们的观众:您最喜欢的调试器提示是什么?请在评论区中分享!
本博文英文原作者: