这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

调试

使用调试功能定位和解决程序中的缺陷

1 - 基础知识和准备

介绍调试的基础知识和使用调试功能前需要的准备工作

在为刚开始学习编程的同学们具体介绍小熊猫C++的调试功能之前,让我们先来看一下调试的基本方法(了解调试的同学可以直接转下一节):

1 调试的基本方法

调试就是找出并修正程序中的缺陷(bug)的过程。其实,即使没有学习过编程,我们也早就做过很多次的“调试”了,只不过调试的对象不是程序,而是数学、物理试题的解题步骤罢了。是的,我们调试程序的过程,和我们检查一道数学或者物理题的解题过程是否正确其实是类似的。

我们在解数学或者物理计算题时,从已知条件出发,每一步会进行一项处理或者计算从而得到一个(中间)结果,后面的步骤在前面的步骤的计算结果上继续进行计算,最后得到结果。所以,检查的过程主要就是看:

每一步计算的逻辑是否正确(根据已经计算出来的结果,是不是该用这个定理来进行下一步计算)? 计算的结果有没有错? 想一想你就会发现,我们在用C或者C++这类命令式编程语言编写的程序其实也是如此。除了输入(cin或者scanf)和输出(printf或者cout)外,程序中的每一条语句要么就是在做计算并保存计算结果(赋值),要么就是在根据某个变量的值来决定下一步该干啥(if、while、for等)。所以,我们在调试程序时主要关注的就是:

计算的逻辑是否正确(if、while、for等语句的条件表达式写的对吗)? 计算的对吗,是否使用了正确中间结果(计算的表达式写错了没有,变量名是否写错了)? 所以,我们调试时最基本的操作就是:

对于if/while/for语句:检查程序在执行这条语句之前,条件表达式的计算结果 对于其他语句:检查程序在执行这条语句前后,相关变量或者内存中的值(或者值的变化) 显然,我们可以直接在相关的语句前后使用printf或者cout等语句输出想要检查的内容,从而进行调试。

但调试工具给了我们更多(很多时候也更加方便)的选择。

2 小熊猫的调试功能

小熊猫C++的调试功能主要可以分为两类:

  • 控制程序的执行:包括断点、单步执行系列工具等,通过“运行”菜单或者调试工具栏来访问
  • 查看程序和内存的状态:包括局部变量面板、内存面板、监视面板、调用栈面板、CPU信息窗口(对话框)等
运行菜单中的调试命令

运行菜单中的调试命令

调试工具栏

调试工具栏

监视面板

监视面板

调试面板

调试面板

3 调试前的准备

在后面的系列文章中,我们将以下面这个程序为例,介绍小熊猫的调试功能。(此程序是正确的,我们只是用它来演示调试功能的使用)

/**
 * 找出整数n的所有质因数
 */
#include <stdio.h>
#include <stdbool.h>

//判断n是否为质数
bool isPrime(int n) {
	if (n<2)
		return false;
	for (int i=2;i<n;i++) {
		if (n%i==0)
			return false;
	}
	return true;
}

int main() {
	int n;
	scanf("%d",&n);
	for (int i=2;i<n;i++) {
		if (n%i==0) { //如果i能整除n
			if (isPrime(i)) { //并且i是质数
				printf("%d\n",i);
			}
		}
	}
	return 0;
} 

绝大多数的调试工具都需要程序在编译时嵌入必要的调试信息,才能够正常工作。小熊猫C++内部所使用的gdb工具也是如此。因此,如果想让程序能被正常调试,必须要使用Debug类型的编译器配置集来编译程序,如下图所示:

选择Debug编译

选择Debug编译

注意:改变编译器配置集后,必须使用“运行”菜单中的“全部重编译”功能强制使用新编译器配置重新编译一遍程序,小熊猫C++目前不会在程序内容本身没有改动过的情况下自动重新编译程序。

2 - 控制程序执行

在调试时控制程序的执行

要查看程序执行到特定位置时的状态,最好当然是能够让它执行到那个位置的时候暂停下来。我们想看什么,就可以看什么。为此,小熊猫C++提供了一系列控制程序执行的功能。

1 启动调试

通过“运行”菜单或者调试工具栏,点击“调试”,就可以让当前程序以调试模式启动运行。当前程序中如果没有断点,则程序启动后会自动停在main()函数的第一条语句处(变量声明除外),如下图所示(注意当前暂停位置所在行左侧的小箭头):

调试按钮

调试按钮

启动调试后,程序暂停在main函数入口处

启动调试后,程序暂停在main函数入口处

2 退出调试

在程序调试过程中,通过“运行”菜单或者调试工具栏,点击“停止执行”按钮,即可停止调试。

停止按钮

停止按钮

3 断点

断点(Breakpoint)就是程序中预设的暂停点。程序在调试运行到断点位置时,就会暂停。

在小熊猫C++中,单击编辑器左侧边栏区域即可设置/取消断点:

点击编辑器左侧栏设置、取消断点

点击编辑器左侧栏设置、取消断点

设置断点后点击”调试“按钮启动调试,程序就会在断点所在的第22行暂停。注意:程序在运行第20行的scanf语句时,会等待用户输入一个整数,这是scanf语句本身的行为特性,和调试没有任何关系

程序暂停在第23行断点处

程序暂停在第23行断点处

4 单步执行

顾名思义,单步执行就是让程序执行一步后暂停。问题在于,到底多少程序算”一步“?在小熊猫C++的运行菜单中,有这么几种单步执行:

  • 单步跳过(Step Over):一行程序算一步。执行完当前行后暂停。
  • 单步进入(Step Into):如果当前行不包含函数调用,则一行程序算一步;如果这行程序中包含对函数的调用,会在进入函数后暂停;如果找不到该函数的符号信息,则在执行完该函数后暂停。
  • 单步跳出(Step Out):退出当前函数后暂停。
单步跳过、单步进入和单步跳出按钮

单步跳过、单步进入和单步跳出按钮

4.1 单步跳过

在上一节中,程序在我们输入300后,暂停在了第23行断点处。注意这一行中包含了对函数isPrime()的调用。并且注意此时变量i的值为2(2是质数)

如果此时(程序暂停在23行时)点击”单步跳过“,程序会在执行完23行后,停在第24行

单步跳过

单步跳过

4.2 单步进入

让我们退出再重新开始调试,输入300后程序再次暂停在了23行断点处。如果此时(程序暂停在23行时)点击”单步进入“,程序会停在第9行isPrime()函数的入口处:

单步进入

单步进入

4.3 单步跳出

我们现在暂停在了函数isPrime()中。现在点击”单步跳出“按钮,程序会返回第23行(isPrime(i)函数执行完了,但是if判断还没执行)

单步跳出

单步跳出

在小熊猫C++的CPU窗口中,还有两个单步执行按钮:”单步执行一条机器指令“和”单步进入一条机器“指令。有兴趣的同学可以猜一猜,然后自己试试它们的作用是什么?

5 继续执行

程序暂停后,通过”运行“菜单或者调试工具栏选择”继续执行”,程序就会继续以调试方式运行,直到遇到下一个断点,或者程序运行结束为止。

接上一节的例子,点击“继续执行”,程序又会停在第23行断点处。但注意此时i的值已经变成了3

继续运行后再次暂停

继续运行后再次暂停

6 GDB Server调试模式

为了支持在Linux下使用终端调试程序,从0.12.5版本开始,小熊猫C++中新增了gdb server调试模式(在Linux下自动缺省启用,目前在Windows下缺省关闭)。可以通过工具菜单的“选项”菜单项打开选项对话框,然后在选项对话框的“调试器”“通用"页中设置是否启用gdb server调试。

使用gdb server调试选项

使用gdb server调试选项

7 中断(仅gdb server模式下支持)

在gdb server模式下,运行菜单和调试工具栏中会多出一个“中断”按钮。此按钮可以暂停运行中的程序。但是由于此时程序可能正在执行某个运行库中的指令(比如,输出内容到控制台窗口),所以小熊猫C++无法在编辑器窗口中获取和显示程序当前暂停的位置。

在Linux操作系统下可以通过调用栈视图了解程序当前的状态。但是Windows下小熊猫C++后台使用的gdb程序尚无法在中断后获取完整的调用栈信息。

Linux系统下程序中断(暂停)时的状态显示

Linux系统下程序中断(暂停)时的状态显示

3 - 查看程序状态

在程序暂停时查看程序的状态

通过小熊猫C++的控制程序执行功能,可以帮我们准确将程序暂停到指定的位置。然后,我们就需要去查看程序的状态了。小熊猫C++提供了一系列工具来帮助我们查看程序的状态

1 局部变量视图

在调试时,调试面板的局部变量视图会自动显示当前函数作用域中的所有局部变量(包括函数参数)。

局部变量视图

局部变量视图

2 监视和监视面板

局部变量视图可以自动显示局部变量的状态,这很好。但是如果某个函数里面的局部变量太多,找我想看的变量很麻烦;或者我想看某个全局变量的状态;或者我想看某个表达式的值,怎么办呢?这时就需要使用监视功能了。

2.1 添加监视

比如,我们想在调试过程中监视&n的值(n的地址)。可以在调试工具栏中点击“添加监视”按钮

添加监视按钮

添加监视按钮

然后在对话框中输入我们要监视的表达式:

添加监视按钮

添加监视按钮

在左侧的监视面板中就可以看到我们要监视的表达式了。因为此时我们没有开始调试程序,所以&n的值中显示“执行以求值”。

gdb(小熊猫C++使用的调试器)支持监视任何C、C++表达式。但请保证在表达式中不会出现无穷递归、无限循环等错误,否则gdb调试器会卡死无法正常使用

2.2 查看监视

在程序调试的过程中,小熊猫C++会在程序暂停时自动更新监视的值。请注意示例程序在main函数中暂停,和在isPrime函数中暂停时,&n的变化(main和isPrime中的n虽然同名,但是是两个作用域不同的变量,在内存中的地址不同)

程序在main函数中暂停

程序在main函数中暂停

程序在isPrime函数中暂停

程序在isPrime函数中暂停

3 调用栈视图

我们按照自顶向下或者模块化的思路设计程序时,会以函数为单位来组织和实现的程序的功能。在调试程序时,我们经常需要知道,函数现在正被谁调用?调用者的状态是怎样的?

调试面板中的调用栈视图为我们提供了程序调用栈(Call Stack)的信息。从下图中我们可以看出,当前程序执行到isPrime函数中,它是在main函数的第30行被调用的。

调用栈信息

调用栈信息

在调用栈视图中双击某一行,小熊猫C++就会自动跳转到对应的程序位置。

点击调用栈视图第二行,跳转到isPrime函数被调用的位置

点击调用栈视图第二行,跳转到isPrime函数被调用的位置

4 内存视图

调试面板的内存视图允许我们在调试时,查看程序进程指定内存位置的值。在地址表达式栏中输入任意返回内存地址的表达式,就可以查看指定位置的内容。例如,下面是我们输入“&n"后的显示结果

5 求值工具

除了监视和局部变量之外,我们还可以使用求值工具来快速计算某个表达式。

注意,求值工具可以执行任意C/C++表达式,包括赋值表达式!其效果和在程序中执行该表达式的作用是相同的。例如,我们在求值输入框中输入n=500,就可以将变量n的值改为500。

6 CPU窗口

CPU窗口让我们可以查看程序对应的机器和汇编指令,以及CPU中各寄存器的状态。通过”运行“菜单的”打开CPU窗口“菜单项即可打开CPU窗口。对于学生和初中级用户而言,这个窗口中的信息可以帮助他们更深入的理解C语言程序的实际工作机理。

4 - 修改变量或内存

在调试时修改变量或内存中的内容

从小熊猫C++ 0.13.2版本开始,我们可以在调试程序时直接修改变量的值或者内存中的数据。初学者可以使用这个功能,更好的理解C语言的变量、数据类型和字符串机制等知识。

1 直接执行表达式

在调试面板的求值框中,我们可以输入任意表达式,包括赋值表达式。通过这一点我们可以对任意变量或者内存地址进行赋值,从而改变它们的值。

在下例中,我们直接在求值框中输入n=10后回车

输入n=10

输入n=10

n的值被修改了

n的值被修改了

2 改变监视变量

在监视面板中,双击要修改的值,就可以对其进行修改。

双击要修改的值

双击要修改的值

进入修改状态

进入修改状态

修改完成

修改完成

可以看到,修改完成后,局部变量视图也同步进行了更新。

3 改变内存的值

和监视面板类似,我们在调试面板的内存视图中,双击要修改的内容,即可直接对其进行修改:

双击要修改的值

双击要修改的值

进入修改状态

进入修改状态

完成修改,注意监视面板中的信息更新

完成修改,注意监视面板中的信息更新