3-9 使用集成调试器:调用栈
现代调试器还包含一个非常实用的调试信息窗口,即调用栈窗口。
当程序调用函数时,我们知道它会标记当前位置,执行函数调用,然后返回。它如何知道返回何处?答案在于调用栈的记录机制。
调用栈call stack是所有被调用至当前执行点的活跃函数列表。每个被调用函数在调用栈中都有对应条目,并标注函数返回时将跳转至的代码行。每次调用新函数时,该函数会被添加至调用栈顶部。当前函数返回至调用方时,会被移除出调用栈顶部,控制权随即返回至其下方的函数。
调用栈窗口call stack window是调试器窗口,用于显示当前调用栈。若未显示该窗口,需手动设置IDE以启用其显示功能。
对于 Visual Studio 用户
在 Visual Studio 中,可通过“调试”菜单 > “窗口” > “调用堆栈”打开调用堆栈窗口。请注意,必须处于调试会话中才能激活此窗口。
对于 Code::Blocks 用户
在 Code::Blocks 中,可通过“调试”菜单 > “调试窗口” > “调用堆栈”找到调用堆栈窗口。
对于 VS Code 用户
在 VS Code 中,调用堆栈窗口会在调试模式下显示,并停靠在左侧。
让我们通过一个示例程序来观察调用栈:
#include <iostream>
void a()
{
std::cout << "a() called\n";
}
void b()
{
std::cout << "b() called\n";
a();
}
int main()
{
a();
b();
return 0;
}
在本程序的第5行和第10行设置断点,然后启动调试模式。由于函数a被首先调用,第5行的断点将首先被触发。
此时,您应该看到类似以下内容:

您的集成开发环境可能存在以下差异:
- 函数名称和行号的格式可能不同
- 行号可能略有差异(相差1)
- 您看到的可能不是[外部代码],而是许多名称怪异的函数。
这些差异无关紧要。
关键在于最顶端的两行。从下往上看,我们发现函数main被首先调用,随后函数a被调用。
函数 a 旁边的第 5 行标记了当前执行点(与代码窗口中的执行标记一致)。第二行的第 17 行则指示控制权返回主函数时将跳转的行号。
提示
函数名后方的行号显示每个函数中即将执行的下一行。
由于调用栈顶部条目代表当前执行函数,此处的行号即为执行恢复时将执行的下一行。调用堆栈中其余条目代表后续将返回的函数,因此其行号表示函数返回后将执行的下一条语句。
现在选择继续调试命令,将执行推进至下一个断点(位于第10行)。调用栈应更新以反映新状态:

你会注意到函数 b 现在位于调用栈的最顶层,这反映出函数 b 是当前正在执行的函数。请注意函数 a 已不再出现在调用栈中。这是因为函数 a 在返回时已被移除。
再次选择继续调试命令,我们将再次触发第5行的断点(因为函数b调用了函数a)。此时调用栈将呈现如下状态:

调用栈中现在包含三个函数:(从底到顶)主函数调用了函数 b,函数 b 又调用了函数 a。
调用栈在配合断点使用时非常有用——当断点被触发时,它能帮助你追溯代码中被调用的函数链,从而了解程序是如何到达该特定位置的。
结论
恭喜你,现在你已经掌握了集成调试器的基本使用方法!通过掌握单步执行、断点设置、监视器和调用栈窗口等功能,您已具备解决几乎所有问题的调试基础。如同许多技能一样,熟练运用调试器需要实践与反复尝试。但我们再次强调:投入时间学习高效使用集成调试器,将在节省程序调试时间方面获得数倍回报!

浙公网安备 33010602011771号