![现代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=1739700506-GZwHDZKS5JtdxK3HqUfSulhSDtKnEbMF-0-8f04f20854678d5cb9694748b2ff254f)
图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=1739700506-5weg9cJV96xKRdGTAXYVBuMiE1zSLg96-0-b8a16dd9deaf33a2250b2408283fae41)
图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=1739700506-8y1eXUc8SwL6FVCKV3EJlILu3Z6581Ci-0-682e200f5c05080f5879abca8a82600e)
图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=1739700506-E4UhPu9FtSac2O7TSSkhQLlGXVyWrbuY-0-7987b7f61ca09503cd6169d656a29daa)
图1-7 插入一个断点
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/59_02.jpg?sign=1739700506-AOq3eQrDsvj2LDC2Zykl2S3W5OK45ZoJ-0-fd306836eeaaa71fa8f3348021cd1648)
图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=1739700506-2cYBHp3kv9TzBWaecUecNSKhi1HTckHD-0-e9c72af38b7a7cbb2301b44b9c422f66)
图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=1739700506-nj7ysTrDhkX14sFThmuXG38qOEej4UbD-0-7baae733918e47068bf3c99d6adcd1dd)
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=1739700506-ZIXsrhytS7nJlE5miHfia73BhRezbCaY-0-705bb646dc5b72feb61891dcd6381328)
3)使用gdb调试程序应该可以看到以下交互式控制台会话:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_03.jpg?sign=1739700506-0oHjlvpmbNg9KWbxqoQC3tjxjn4jFBdi-0-f2d93372bb91992603e25163e8cd8eac)
4)要插入断点,可以使用break命令,该命令需要一个参数,该参数对应源文件的名称和要插入断点的行(用冒号分开)。例如,假设我们想在main.cpp的第一行(对应代码清单1-8的第5行,是否需要调整位置取决于编写代码的方式)中断。在(gdb)提示符下可使用以下命令创建断点:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_04.jpg?sign=1739700506-3poPv0m9NY93HNELpuBI5r8HPshkPOeq-0-0826bb8f0c4f4e85897d57720658e311)
5)我们也可以通过函数名告诉gdb在某个特定的函数处中断:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_05.jpg?sign=1739700506-wriBuTjbMSeQeCtoKoAbBlk45DNrxKDl-0-05306b2359dbe0490af6e68ddd6e8309)
6)不管怎样,现在可以执行程序了:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_01.jpg?sign=1739700506-fU1i85eOUo2fYm3kf6qyhrsJLq482qKG-0-ba952b2f7f4b7347ace91fd281b26fd4)
7)要单步调试指令,可用step命令来追踪程序的每一行,包括函数内部的单步调试:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_02.jpg?sign=1739700506-9sPYsD7jY3M9T6JOmb9mH4gGQ6iMlUWz-0-6d0211a184c1f750b67c4222dd5a57b5)
8)要继续单步调试,可按<Enter>键,重复上一个命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_03.jpg?sign=1739700506-NDCqAMD9U8W5wARCRmvuhKxMJ1x7gLdZ-0-ec1432eab23d9ce85edabb0ebba94533)
9)要跳出函数的调用,可以使用finish命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_04.jpg?sign=1739700506-iic8JoZi4MEhIt4zLbKViKVypiz65XiC-0-ea30cc088a3b2e4b51d809ed0f303399)
10)要执行一条指令而不进入函数,可以使用next命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_05.jpg?sign=1739700506-fYIzkohCO0Jt1aa1qG10Mvn4X3Ovlrrk-0-ff3ae3426eca8b3ddc46a9d9c240980c)
11)要检查变量的当前值,可以使用info locals命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_06.jpg?sign=1739700506-KiqLH5MCHocwX5w7csPzFS5PWf6W4FtM-0-592ccc7a9a015c10d48e499be7452aa0)
注意,任何尚未被初始化的变量都不会有合理的值。
12)若要继续执行直到下一个断点(或程序结束),可以使用continue命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_07.jpg?sign=1739700506-auoDAZbRlv0rZC9MCCUdjzzQomky0owo-0-c5f42ce839de58a4d2ce5964b3b6cc20)
13)使用quit命令可以随时退出gdb。
GDB支持更多的功能。欲了解更多信息,请查看https://sourceware.org/gdb/current/onlinedocs/gdb.html/。