![现代C++编程:从入门到实践](https://wfqqreader-1252317822.image.myqcloud.com/cover/893/48593893/b_48593893.jpg)
1.4 调试
软件工程师最重要的技能之一是高效、有效的调试能力。大多数开发环境都有调试工具。在Windows、macOS和Linux上,这些调试工具都很好。学会使用这些工具是一项投资,可以很快得到回报。本节将简要介绍如何使用调试器来逐步调试代码清单1-8中的程序。你可以跳到与自己的环境最相关的部分。
1.4.1 Visual Studio
Visual Studio有一个内置的优秀调试器。建议在Debug配置中调试程序。这将使工具链以增强调试体验为目标。在Release模式下进行调试的唯一原因是诊断一些在Release模式下出现而在Debug模式下没有出现的罕见情况。
1)打开main.cpp,找到main的第一行。
2)单击main第一行对应的行号左边的空白处,插入一个断点,此时会出现一个红色的圆圈,如图1-4所示。
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/57_01.jpg?sign=1739395897-ojQqLefCQ8B3dsIfVLXGlRrASMMnoxT0-0-69958142400db5a0455baed0375c25d2)
图1-4 插入一个断点
3)选择Debug(调试)→Start Debugging(启动调试)。程序将运行到插入断点的那一行。调试器将停止程序的执行,这时会出现一个黄色的箭头,指示要运行的下一条指令,如图1-5所示。
4)选择Debug(调试)→Step Over(单步跳过)。单步跳过是在不“进入”任何函数调用的情况下执行指令。默认情况下,单步跳过的键盘快捷键是<F10>。
5)因为下一行将调用step_function,所以选择Debug(调试)→Step Into(单步调试)来调用step_function并在该函数的第一行中断。通过单步调试或单步跳过可继续调试这个函数。默认情况下,单步调试的键盘快捷键是<F11>。
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/58_01.jpg?sign=1739395897-u2wmM2tIr6m4mItO34tiTqXWWGuvyGWI-0-07074018bb9fa4470dfc2c57224022d8)
图1-5 调试器在断点处停止执行
6)要让执行返回到main,请选择Debug(调试)→Step Out(单步跳出)。默认情况下,单步跳出的键盘快捷键是<Shift+F11>。
7)通过选择Debug→Windows→Auto,检查Autos窗口。我们可以看到一些重要变量的当前值,如图1-6所示。
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/58_02.jpg?sign=1739395897-CRIJ1ffDQFEVlIQBJk3CVaLiqXvAESWr-0-23e6dfe32fddd3ba7c8636ab1ebf892a)
图1-6 Autos窗口显示当前断点处的变量值
可以看到,num1被设置为42,result1被设置为1。为什么num2有一个乱七八糟的值?因为num2初始化为0的过程还没有发生:这是下一条指令要执行的。
注意 调试器刚刚强调了一个非常重要的底层细节:分配对象的存储空间和初始化对象的值是两个不同的步骤。第4章将介绍更多关于存储空间分配和对象初始化的知识。
Visual Studio调试器支持更多的功能。欲了解更多信息,请查看Visual Studio文档。
1.4.2 Xcode
Xcode也有一个内置的优秀调试器,它已完全集成在IDE中。
1)打开main.cpp,找到main的第一行。
2)单击第一行,然后选择Debug(调试)→Breakpoints(断点)→Add Breakpoint at Current Line(在当前行设置断点),此时会出现一个断点,如图1-7所示。
3)选择Run(运行),程序将运行到插入断点的那一行。调试器将停止程序的执行,此时会出现一个绿色的箭头,指示下一条要运行的指令,如图1-8所示。
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/59_01.jpg?sign=1739395897-kxWAgIYHAaVAzEWSTSjEkWe1GDPCpnKD-0-1eef7b054c5663cd48b0ff39f15d4517)
图1-7 插入一个断点
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/59_02.jpg?sign=1739395897-4R881N45UfWha7MyCxxc1uOQUguJeb95-0-34cd3ccef15292988a909dde02cbc2b6)
图1-8 调试器在断点处停止执行
4)选择Debug(调试)→Step Over(单步跳过)来执行指令,而不“进入”任何函数调用。默认情况下,单步跳过的键盘快捷键是<F6>。
5)因为下一行代码会调用step_function,所以选择Debug(调试)→Step Into(单步调试)来调用step_function并在该函数第一行中断。通过单步调试或单步跳过可继续调试这个函数。默认情况下,单步跳过的键盘快捷键是<F7>。
6)要让执行返回到main,请选择Debug(调试)→Step Out(单步跳出)。默认情况下,单步跳出的键盘快捷键是<F8>。
7)检查main.cpp屏幕底部的Autos窗口,可以看到一些重要变量的当前值,如图1-9所示。
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/59_03.jpg?sign=1739395897-v8MqqdbsaKj7crHRIarqNXZl90WcgYrR-0-63168dfcc53fa880067b7f0ca2951b77)
图1-9 Autos窗口显示当前断点处的变量值
可以看到,num1被设置为42,result1被设置为1。为什么num2有一个乱七八糟的值?因为num2初始化为0的过程还没有发生:这是下一条指令要执行的。
Xcode调试器支持更多的功能。欲了解更多信息,请查看Xcode文档。
1.4.3 用GDB和LLDB对GCC和Clang进行调试
GNU项目调试器(GNU project DeBugger,GDB)是一个强大的调试器(https://www.gnu.org/software/gdb/)。我们可以使用命令行与GDB交互。要在用g++或clang++编译时启用调试支持,必须添加-g标志。
包管理器很可能有GDB。例如,要用高级包工具(APT)安装GDB,请输入以下命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_01.jpg?sign=1739395897-5hYzBY8ASnfv6YLC7FHA2tTX9xFQBe7y-0-4dbf6823f8fdb572a59689d507a18d2f)
Clang也有一个很好的调试器,叫作LLDB(Low Level DeBugger),详见https://lldb.llvm.org/。它与本节中的GDB命令兼容,所以为了简洁起见,这里不具体介绍LLDB。我们可以使用LLDB来调试由GCC编译的程序,也可以使用GDB来调试用Clang编译的程序。
注意 Xcode在后台使用LLDB。
使用GDB调试代码清单1-8中的程序,请遵循以下步骤:
1)在命令行中,切换到存放头文件和源文件的文件夹。
2)启用调试支持的同时编译程序:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_02.jpg?sign=1739395897-kmyU2PO5Bp4HiIXv7UgIczHgG6FTgROS-0-3f0da069742ab7640d6c859e50b90f25)
3)使用gdb调试程序应该可以看到以下交互式控制台会话:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_03.jpg?sign=1739395897-GyOWPKASUp9aUM2tYYS4DAys0Q7zO6RV-0-f5f8b4ec616ae43fed7626e1cd28aa20)
4)要插入断点,可以使用break命令,该命令需要一个参数,该参数对应源文件的名称和要插入断点的行(用冒号分开)。例如,假设我们想在main.cpp的第一行(对应代码清单1-8的第5行,是否需要调整位置取决于编写代码的方式)中断。在(gdb)提示符下可使用以下命令创建断点:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_04.jpg?sign=1739395897-B9Jd66lAZc7Lep2z6c4alibCOMg3rg2m-0-30346c3d126d52a1137c081fea8ee555)
5)我们也可以通过函数名告诉gdb在某个特定的函数处中断:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_05.jpg?sign=1739395897-s8wRCBwlxrfRaPN2ozh7fbKhu7jhZsrC-0-fe23e348db2f3c5d140e557a3bfa3111)
6)不管怎样,现在可以执行程序了:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_01.jpg?sign=1739395897-gmPf1T1jon2IwPFAy83u1tkv11UoWRT7-0-23e8b66234428f2da3cfb1478f988758)
7)要单步调试指令,可用step命令来追踪程序的每一行,包括函数内部的单步调试:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_02.jpg?sign=1739395897-wTyk38l8kRU0CthsAwAMsB1yEyjj5CS0-0-99f5e9be9b83edf8223534bd9b92ad43)
8)要继续单步调试,可按<Enter>键,重复上一个命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_03.jpg?sign=1739395897-IkFi4ZU0IQoFb4vYqMyyVTMQzaxVId1M-0-7391f915a4c2eabd1526b23bb0d0ffa2)
9)要跳出函数的调用,可以使用finish命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_04.jpg?sign=1739395897-HFd8NiClNPtiqUfGB5ELePMtoQXnxXLO-0-65b83fd5d237aa4f1d991661ff7741fb)
10)要执行一条指令而不进入函数,可以使用next命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_05.jpg?sign=1739395897-QwgfH0HrdoZDwmlTOWa5InsQjS6idZZM-0-c19d1fac889e2c845afa1936b1a22d82)
11)要检查变量的当前值,可以使用info locals命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_06.jpg?sign=1739395897-O4G4vDHduKPhwK7cRktrfghincewVX5w-0-d3eeaa106c95eeb0ede1b80a1b0f2edd)
注意,任何尚未被初始化的变量都不会有合理的值。
12)若要继续执行直到下一个断点(或程序结束),可以使用continue命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_07.jpg?sign=1739395897-D5aaoAlQXUhriwpXS4GZpwH95ix2CCPL-0-49c714b3334cb95bce1832842d7d6d7a)
13)使用quit命令可以随时退出gdb。
GDB支持更多的功能。欲了解更多信息,请查看https://sourceware.org/gdb/current/onlinedocs/gdb.html/。