目录
前言
- 本文使用的是Visual Studio 2022社区版,但在老版本上依然适用(例如2019版)。
- 本文旨在简单介绍一些调试的小技巧,进阶的调试技巧以后再做总结。
- 本文基于Windows平台
正文
一、打断点
启用调试,第一步需要打断点:
注意:启动调试后,程序会执行到第一个断点出暂停,这里的第一个断点指的不是位置上的第一个(即代码行最靠前的那一个),而是逻辑上的第一个。例如上图,第24行的断点是最先执行的那一个。
二、逐语句执行和跳出执行
逐语句执行也叫“单步执行”或“逐行执行”,如果调用了一个函数,那么会进入这个函数中,而不是跳过函数。
注意:这里的“逐行”,不是物理意义上的每一行,而是逻辑上的一行代码。
例如,上面第24行,判断条件里调用了2个函数,那么,会首先进入add()函数,返回后再进入sub()函数。
注意:C++编译器对条件判断有一个优化,对于24行,如果add()为false,那就不会继续执行sub()了,如果add()为true,才会继续进入sub()函数。
跳出执行的用处是,如果进入函数后,不再希望用单步执行走完函数体内剩余的代码,那么可以跳出执行,直接返回:
三、逐过程执行
特点:无论当前代码行有多少个函数调用,都不会进入到函数中,而是直接进入到下一行代码并暂停。
三、运行到光标处
在没启动调试的时候,直接在想要定位的代码行处右键,选择运行到光标处,那么就会自动设置一个一次性断点,开始调试:
注意:一次性断点的优先级是低于其它断点的,如果调试之前在某个位置打了断点A,并且这个断点在一次性断点之前(逻辑上),那么启用调试后,会首先来到断点A处。
四、多次执行代码
如果有些地方没弄清楚,那么可以在不重新打开调试的情况下,多次执行某些代码。
例如:
断点打在10行,执行完10行后,来到11行,但如果我还想再执行一次第10行,那就是“多次执行代码”了,方法很简单,就是:用鼠标把那个黄色箭头拖到第10行:
连续执行2次第10行:
五、快速监视
在调试过程中,如果想要快速查看某个对象的信息,那么快速监视就挺有用,同时,还可以修改这个对象的值。
方法是:选中某个变量,右键:
六、监视窗口
调试的时候,如果要查看的变量很多,就需要用监视窗口,同时,可以打开多个监视窗口:
注1:和快速监视一样,监视窗口也能修改变量的值。只不过对于string这类较为复杂的类型来说,修改就相对麻烦,不能一次性修改,而是找到每个字符对应的位置,再修改:
注2:也可以监视一些表达式(有些不行,例如构造、析构、类型转换、预处理器宏等):
八、内存查看
目前还没有从内存层面去找bug,所以就举个查看内存的例子。
首先,启用调试,然后打开内存窗口:
默认窗口没有什么内容,只有一些随机的值:
当需要查看某个变量的内存占用时,只需要把这个变量拖到内存窗口:
同理,把wa也拖上去,对比a和wa的内存占用情况,可以看到wa是宽字符,每个字符占2个字节:(最后有一个结束符,也需要占一个字符大小的内存空间)
九、局部变量
- 用来查看当前作用域下的变量:
test()里的p:
test0()里的p:
- 变量太多时,可以筛选想要显示的变量:
十、调用堆栈
可以查看函数的调用情况,每一个函数调用叫做“帧”,也称为“栈帧”;
栈底的函数最先被调用,栈顶的函数最后被调用:
十一、assert的使用
首先看一个很熟悉的窗口:
这个实际上就是通过assert产生的,如果出现,不要点“中止”,而是点“重试”,这样就能找到代码出错的地方:
注意:
- 要导入头文件 #include <assert.h>
- 不要在assert里使用函数调用、对变量赋值!
关于第2点,《C++ Primer》第216页讲到,如果源文件定义了NDEBUG宏,那么assert就会失效,从而assert里的函数调用和赋值等操作都会被忽略,导致后面的错误!下面举例:
如果没有定义NDEBUG宏,那么assert是生效的,因此add函数会把相加的结果赋值给c:
#include <assert.h>
int add(int a, int b)
{
return a + b;
}
void test()
{
int a = 10, b = 10;
int c = 0;
assert((c = add(a, b)) == 20); // 调用函数并赋值
cout << c; // 输出20
}
如果定义NDEBUG宏:
#define NDEBUG
#include <assert.h>
int add(int a, int b)
{
return a + b;
}
void test()
{
int a = 10, b = 10;
int c = 0;
assert((c = add(a, b)) == 20); // 这条语句会被忽略
cout << c; // 输出0
}
十二、条件断点
通过举例来讲解:假如有一个for循环,需要在到达某个条件的时候,调试才停下来,而不是从一开始就停下来,那么此时就可以用条件断点:
启动调试:
十三、函数断点
当我们知道一个函数名,但不知道函数具体在哪里的时候;或者函数被重载的时候,希望在每次调用函数的时候能够暂停;此时需要用到函数断点:
启动调试,就会自动来到add函数的位置: