![现代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=1739283870-2po4IJOAFO2YIOqsPwgrdAnwvkNmEP6w-0-bc2bf06903f8d3894440de78a3352db3)
图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=1739283870-5HVPnFutXAk0CeJPShuQQA69dcvju2U2-0-a73a28729e5599fd7251b0d4af4b21c4)
图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=1739283870-xcGrNNFl59Q3jDSaWAX87SzGGXxBibwL-0-50e7d864c8464f908ef5120d3e673835)
图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=1739283870-kBw4XfvyWj4BYQTB5iNDjAU7Zwm8tqu0-0-7ea832790e2d17bbb02e5baacd154325)
图1-7 插入一个断点
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/59_02.jpg?sign=1739283870-SlIHhS7VZdwC7roLyNHJz8hIOMUNwBTg-0-7111fbeada041c1d9f0019844098a4d7)
图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=1739283870-n6RyUL6TbjJb41TQ3BgHdazFUQw0Dsc5-0-2549c29319668b696a84cb87aac6feb0)
图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=1739283870-wLTdk3rvr9XElQVbzCd76j6s34JyXvYE-0-276c1c71e945e2987e4b9fd72bfaa3bd)
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=1739283870-ykrAiHKDnwfJKNOPB6joK9MJNYgJSgmJ-0-96128b2817bba213ffd3e03c625c15ed)
3)使用gdb调试程序应该可以看到以下交互式控制台会话:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_03.jpg?sign=1739283870-NECTptCbOEpRTSgzEse7YtcXJzG7Ejbo-0-a68c0dc2b3d2d26cc971fec93a4ceaba)
4)要插入断点,可以使用break命令,该命令需要一个参数,该参数对应源文件的名称和要插入断点的行(用冒号分开)。例如,假设我们想在main.cpp的第一行(对应代码清单1-8的第5行,是否需要调整位置取决于编写代码的方式)中断。在(gdb)提示符下可使用以下命令创建断点:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_04.jpg?sign=1739283870-OY6fBBLYcere1KSZPmo2m7gwYKfN4vsY-0-c03a8486ad593776b415e7a05584db18)
5)我们也可以通过函数名告诉gdb在某个特定的函数处中断:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/60_05.jpg?sign=1739283870-7DiF6wdbXEr0E3JzTnpheXFov4SH1KOx-0-88c7d0f1b05f61ab2842bc4c3be7cd49)
6)不管怎样,现在可以执行程序了:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_01.jpg?sign=1739283870-wFI4OWsCqxjA6JNStnspHJ1A8PsWX9Fy-0-c5a83f0e6f40618a556cf03755ebc084)
7)要单步调试指令,可用step命令来追踪程序的每一行,包括函数内部的单步调试:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_02.jpg?sign=1739283870-dzpbSJOLCWlXjZab1LU8xD1vJh8tkR98-0-8f4ef9a392396b4c01bf95b330d1d888)
8)要继续单步调试,可按<Enter>键,重复上一个命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_03.jpg?sign=1739283870-KaDFKDcVjgjdgXKtTqcK1m3pjchEsNUU-0-22217768e58857bb98621b6709ebd4ca)
9)要跳出函数的调用,可以使用finish命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_04.jpg?sign=1739283870-wpKf8Fg59tdGLtv4K6fqaAFRNPiXVQTj-0-7a879e32dd447a99386b0ea7b51925d7)
10)要执行一条指令而不进入函数,可以使用next命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_05.jpg?sign=1739283870-dNSYO2i1Wew3Qu9X4UZJ5gzUygSzmvUK-0-a70774b9816f0eb2b433cc4b15882663)
11)要检查变量的当前值,可以使用info locals命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_06.jpg?sign=1739283870-9bW6GvZ2jpMDR8kxVFYhQh0hFNSeUqlH-0-7e69d11193983b7d2a2a869ee394f3b7)
注意,任何尚未被初始化的变量都不会有合理的值。
12)若要继续执行直到下一个断点(或程序结束),可以使用continue命令:
![](https://epubservercos.yuewen.com/21A689/28235429907469906/epubprivate/OEBPS/Images/61_07.jpg?sign=1739283870-bAods6E0NlV0N2GiIwxjSlplxyW5EUwo-0-b24e5d8a03060ac82b57b2f4923fdbaa)
13)使用quit命令可以随时退出gdb。
GDB支持更多的功能。欲了解更多信息,请查看https://sourceware.org/gdb/current/onlinedocs/gdb.html/。