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所示。
图1-4 插入一个断点
3)选择Debug(调试)→Start Debugging(启动调试)。程序将运行到插入断点的那一行。调试器将停止程序的执行,这时会出现一个黄色的箭头,指示要运行的下一条指令,如图1-5所示。
4)选择Debug(调试)→Step Over(单步跳过)。单步跳过是在不“进入”任何函数调用的情况下执行指令。默认情况下,单步跳过的键盘快捷键是<F10>。
5)因为下一行将调用step_function,所以选择Debug(调试)→Step Into(单步调试)来调用step_function并在该函数的第一行中断。通过单步调试或单步跳过可继续调试这个函数。默认情况下,单步调试的键盘快捷键是<F11>。
图1-5 调试器在断点处停止执行
6)要让执行返回到main,请选择Debug(调试)→Step Out(单步跳出)。默认情况下,单步跳出的键盘快捷键是<Shift+F11>。
7)通过选择Debug→Windows→Auto,检查Autos窗口。我们可以看到一些重要变量的当前值,如图1-6所示。
图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所示。
图1-7 插入一个断点
图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所示。
图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,请输入以下命令:
Clang也有一个很好的调试器,叫作LLDB(Low Level DeBugger),详见https://lldb.llvm.org/。它与本节中的GDB命令兼容,所以为了简洁起见,这里不具体介绍LLDB。我们可以使用LLDB来调试由GCC编译的程序,也可以使用GDB来调试用Clang编译的程序。
注意 Xcode在后台使用LLDB。
使用GDB调试代码清单1-8中的程序,请遵循以下步骤:
1)在命令行中,切换到存放头文件和源文件的文件夹。
2)启用调试支持的同时编译程序:
3)使用gdb调试程序应该可以看到以下交互式控制台会话:
4)要插入断点,可以使用break命令,该命令需要一个参数,该参数对应源文件的名称和要插入断点的行(用冒号分开)。例如,假设我们想在main.cpp的第一行(对应代码清单1-8的第5行,是否需要调整位置取决于编写代码的方式)中断。在(gdb)提示符下可使用以下命令创建断点:
5)我们也可以通过函数名告诉gdb在某个特定的函数处中断:
6)不管怎样,现在可以执行程序了:
7)要单步调试指令,可用step命令来追踪程序的每一行,包括函数内部的单步调试:
8)要继续单步调试,可按<Enter>键,重复上一个命令:
9)要跳出函数的调用,可以使用finish命令:
10)要执行一条指令而不进入函数,可以使用next命令:
11)要检查变量的当前值,可以使用info locals命令:
注意,任何尚未被初始化的变量都不会有合理的值。
12)若要继续执行直到下一个断点(或程序结束),可以使用continue命令:
13)使用quit命令可以随时退出gdb。
GDB支持更多的功能。欲了解更多信息,请查看https://sourceware.org/gdb/current/onlinedocs/gdb.html/。