使用集成调试器:调用堆栈

现代调试器包含一个调试信息窗口,它在调试程序时非常有用,那就是调用堆栈窗口。

当程序调用一个函数时,你已经知道它会在当前位置做一个标记,进行函数调用,然后返回。它是如何知道要返回到哪里的呢?答案是它会在调用堆栈中进行跟踪。

调用堆栈是一个列表,列出了所有被调用以到达当前执行点的活动函数。调用堆栈为每个被调用的函数都有一个条目,以及当函数返回时将返回到哪一行代码。每当调用一个新函数时,该函数就会被添加到调用堆栈的顶部。当当前函数返回给调用者时,它就会从调用堆栈的顶部移除,控制权返回到它下面的那个函数。

调用堆栈窗口是一个调试器窗口,它显示当前的调用堆栈。如果你没有看到调用堆栈窗口,你需要告诉 IDE 显示它。

对于 Visual Studio 用户

在 Visual Studio 中,可以通过"Debug"菜单 > “Windows” > “Call Stack"找到调用堆栈窗口。请注意,你必须处于调试会话中才能激活这个窗口。

对于 Code::Blocks 用户

在 Code::Blocks 中,可以通过"Debug"菜单 > “Debugging windows” > “Call stack"找到调用堆栈窗口。

对于 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 行的断点会首先被触发。

此时,你应该会看到类似以下内容:

Code::Blocks安装

你的 IDE 可能会有一些不同:

  • 函数名称和行号的格式可能不同
  • 行号可能略有不同(相差 1)
  • 除了[External Code],你可能会看到许多其他奇怪命名的函数

这些差异是无关紧要的。

这里相关的是最上面的两行。从下往上,我们可以看到函数main首先被调用,然后函数a被调用。

函数a旁边的第 5 行显示了当前的执行点(与代码窗口中的执行标记相匹配)。第二行的第 17 行表示当控制权返回到函数main时将返回到的行。

提示

函数名称后面的行号显示了每个函数中将要执行的下一行。

由于调用堆栈顶部的条目代表当前正在执行的函数,因此这里的行号显示了恢复执行时将要执行的下一行。调用堆栈中的其余条目代表将要返回的函数,因此这些的行号代表函数返回后将要执行的下一个语句。

现在,选择继续调试命令,将执行推进到下一个断点,它将在第 10 行。调用堆栈应该更新以反映新的情况:

Code::Blocks安装

你会注意到函数b现在是调用堆栈的第一行,反映了函数b是当前正在执行的函数。请注意,函数a不再出现在调用堆栈上。这是因为函数a返回时被从调用堆栈中移除了。

再选择一次继续调试命令,我们将再次触发第 5 行的断点(因为函数b调用了函数a)。调用堆栈将如下所示:

Code::Blocks安装

现在调用堆栈上有三个函数:(从下往上)main,它调用了函数b,函数b又调用了函数a

当你的断点被触发,你想知道哪些函数被调用来到达代码中的那个特定点时,调用堆栈与断点结合使用非常有用。

总结

恭喜你,你现在掌握了使用集成调试器的基础!通过使用单步执行、断点、监视和调用堆栈窗口,你现在拥有了调试几乎所有问题的基本技能。像许多事情一样,熟练使用调试器需要一些练习和试错。但再次强调,花时间学习如何有效使用集成调试器,将在节省调试程序的时间上得到许多倍的回报!

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"