更新中 …
基础的调试技巧基本更新完毕,visual studio 提供了强大的调试功能,许多东西需要大家动手体验

环境

  1. 操作系统:Windows 10
  2. IDE:Visual studio 2017
    【注】不同版本的 Visual studio 可能会有细微的差别

调试器的基本使用

更改执行流

当调试执行到 21 行时,想要回到前面的行,只需要抓住黄色箭头拖动其回到前面的行(如 18 行)即可从那里开始执行
在这里插入图片描述

断点

当我们需要在代码执行的过程中,从某条语句开始,停下来一步一步,一条一条地过每一条代码的时候,这是我们就需要用到断点。

基本的断点操作

  1. 设置基本断点
    在这里插入图片描述

如上图,在你需要停下来的语句中,在其左侧的那条竖栏处左击,就会出现一个红色圆点,这表示当你运行代码的时候(不是运行 .exe 程序)会在这里停下来。

【注】程序只会从断点开始的地方停下来,而之前的语句将会快速执行过去。

  1. 进入调试
    在这里插入图片描述

按上图红框所示,进入调试状态。

  1. 调试界面简述
    在这里插入图片描述

在调试程序启动后,程序快速运行至 ① 处停下等候你的下一步操作,可以看见,红色的 ① 上面多了一个黄色的向右箭头,这表示当前程序运行到了这条语句。

② 处有三个操作符:
向下的箭头表示,进入更深层次的代码中。比如,当黄色箭头运行到一个调用的函数时,我想进入看看这个函数内部究竟是怎么运行的,就可以用这个箭头。

弯曲向下的箭头表示在当前程序中一条一条执行。那上一个箭头中的例子举例,我并不想进入这个调用的函数,仅仅想在当前页面执行函数,就用这个箭头。

向上的箭头,表示跳出更深层次的代码。还是那最开始的例子举例,如果你进入了更深层次的函数中,但这个函数太冗长了,你想跳出来,回到本来执行的页面,就应该用这个箭头。

③ 处有一个红色的方框,这表示停止调试,退出调试。

④ 处,可以看到下面有两个输出数据的框。如果你仔细观察,这些框的底部还有一排隐藏的框,比如 Autos 的右边还有 Locals,Watch1 这些隐藏起来的框。这些输出框可以帮助你进行调试。

  1. 【补】快速执行到某行代码
    我们对上面的代码进行了微调,新增了几条语句用于演示这个功能。
    在这里插入图片描述
    在上图中,我们调试代码,程序运行到断点处停止,但是我觉得循环太久了,想跳到下面的第三条 i++ 语句处,那么我们可以直接把光标移动到 i++ 上,然后它的左边会出现一个绿色的向右箭头(如图中所示),当我们点击这个箭头,就可以快速执行到这个语句,并且它之前的代码也会执行到。
  2. 快速从上一个断点执行到下一个断点:具体的做法是按 F5。在下图中,我设置了两个断点,分别在第七行和第二十五行,在两个断点之间有一些变量和一个循环 100 的循环体。

设我们从第七行开始执行,如果按 F5,则程序直接执行到第二十五行,而不是像单步调试一样一步步前进。注意,即使你从第七行开始进行单步调试,也可以通过 F5快速执行到第二十五行。
在这里插入图片描述

跟踪点

跟踪点的作用是可以在 Output 窗口中打印出你设置的一些信息。
在这里插入图片描述
在这里插入图片描述
我们来举一个例子,还是之前的代码,我们将断点打在 cout << "当前的 i 值: " << i << endl; 上,我想在 Output 窗口输出一些打印的信息。

将鼠标放在断点处,弹出的小框中选择齿轮在这里插入图片描述样式。点击后就可以进行设置。
在这里插入图片描述
在上图中,通过 Actions 中的输入框,可以输入你想打印的信息。这里我想输出变量 i 的数据,于是用 {} 包裹住 i

勾选 Continue execution 表示不将跟踪点视为断点,也就是程序在调试时不会在此停顿。

下面我来看看效果,Output 输入框中打印出了我们想要的信息。
在这里插入图片描述
这里有更多的特定信息
在这里插入图片描述

条件断点

所谓条件断点,是断点处,当满足了你设置的条件时才会触发。一般可以和跟踪点搭配使用。
在这里插入图片描述
继续沿用之前的代码,在上图中,我们可以看到大的红色框包含了条件断点的三种类型,分别是条件表达式,命中次数和过滤器,下面我们一个一个来讲。

条件表达式

条件表达式是指满足了给定的条件表达式,断点或者跟踪断点才会被触发。
在这里插入图片描述
如上图,设置 i%2 == 0,表示当该断点处,该条件为真(is true)时,跟踪断点才会被触发,记录相应的输出信息。

效果如下,Output 框中确实只输出了偶数次的记录。
在这里插入图片描述
下面再举一个对普通断点设置条件的例子。
如图,当 i 循环到 5 的时候,该断点会被触发。
在这里插入图片描述
具体怎么设置见下图:鼠标移动到普通的断点上,然后选择齿轮图标即可进入上图的状态,设置即可。
在这里插入图片描述

命中次数

如图,当命中次数 >= 5 时,断点或者跟踪断点被触发执行。
在这里插入图片描述
如上图,我们设定当此处的断点被执行了 5 次后触发。

过滤器

过滤器用于显示指定的输出信息,在这里不详述,因为我也没用过这个功能,大家有兴趣的,可以通过参考部分的链接进行学习。

函数断点

函数断点用于专门进行调用函数时的中断操作,适合只知道函数名字不知道函数位置,或者中断多个重名函数(重载函数)等。

具体操作如下
在这里插入图片描述
在输入框中输入函数的名字
在这里插入图片描述
我们再次进行调试的时候,它就会自动将断点打在 FuncA 的位置
在这里插入图片描述

缩小函数范围

诸如有时候函数会出现重载的问题,输入一个函数名会中断所有的同名函数,那么要缩小范围,可以书写更详细的函数细节,如存在重载的函数 Func(),我只想中断其中形参为 intstring 的函数,则输入 Func(int, string); 为限定

数据断点

数据断点用于变量值发生更改的时候中断执行,如果未发生更改,则执行不会中断。这里讲一种便捷的方式,具体做法如下:
首先,我们要进入调试状态,然后选择要操作的变量,通过 Add Watch 将其添加到 Watch 窗口
在这里插入图片描述
当代码第一次执行到变量的时候将其添加数据断点
在这里插入图片描述
当执行到变量值发生改变的时候,就会产生提示
在这里插入图片描述

断点管理

大型的项目里,在调试的时候,往往有许多的断点,如何管理这些断点如何知道这些断点在什么位置呢?通过 【Debug】-【Windows】-【BreakPoints】或快捷键 ctrl + alt + B 可以查看整个 solution 中的断点。
在这里插入图片描述

各种窗口

最开始我们讲到了,在代码区域的下方,有各种各样的窗口,这些窗口具有不同的功能,用来辅助我们进行 debug,输出响应的信息。

Error List 窗口

error list 窗口可以说是在调试的时候使用频率非常多的窗口了,当你在 build(构建)时,如果程序有编译或链接的错误,都会在这里体现出来,下面我们来举一个例子。

在下图中,我通过一段示例代码,使得程序在 build 的时候报出了一个错误(虽然显示的是 2 Errors,但实际是同一个问题),这是一个非常典型的不能解析外部符号引用的错误
在这里插入图片描述
关于本例是如何造成这个错误的,在这里不阐述,只是通过这个错误看一下 Error List 提供了哪些功能。

在主要的窗格部分,从左至右以此排列的是

  1. Code:这里显示的是错误的类型,如图中是 LNK2019LNK1120,LNK 是 link 的缩写,表示是一个链接阶段的错误,20191120 是错误的类型,注意,这两个都是可以点击的超链接,比如点击 LNK2019 会跳转到 MSDN 提供的关于解释该错误原因的网页,方便用户进行调试
    在这里插入图片描述
  2. Description:提供了关于当前错误的描述
  3. Project:展示了当前错误所在的 Project(项目),当你的 Solution 有多个 Project 时,可以让你定位错误来源
  4. File:即错误具体产生的文件,如上图是在 main.obj 文件中产生的错误,main.objmain.cpp 经过编译,汇编后产生的目标对象文件
  5. Line:显而易见表示行数

Autos 和 Local 窗口

  1. 在 C++ 中,Autos 窗口显示前三个代码行中的变量
    在这里插入图片描述
  2. 显示函数返回值:Autos 窗口也会自动显示当前执行流作用域中,已经执行的函数的返回值。若某作用域中有多个子函数,每个子函数都执行完毕,Autos 仅显示最新执行完的函数的返回值
    在这里插入图片描述
    Locals 窗口显示当前作用域范围中的变量。

监视窗口

在这里插入图片描述
将变量加入到监视窗口,你可以通过该窗口持续关注变量的变化。当超出该变量的范围时,监视窗口的变量变灰。
在这里插入图片描述

函数调用堆栈

在这里插入图片描述
函数调用堆栈可以很方便的让我们在调试时定位当前执行函数的位置,尤其是项目中出现了异常或者错误,而且异常错误是在引入的 DLL 中,这就可以用于定位错误的位置。

如图上图所示的代码中,main,FuncA,FuncB 是以此嵌套的关系,在 main 中调用 FuncA 处设置断点,以此进入调用函数,可以看到函数的调用关系。

除了 Call Stack 可以查看函数调用栈外,Stack Frame 也有相同的作用,一般开发中,Stack Frame 我使用的更多,因为它是固定在工具栏中的
在这里插入图片描述

调试运行中的程序

很多时候,我们需要调试一个已经 build 好的 .exe 的程序(一个运行的进程),这时我们就需要使用 vs 提供的 attach 的功能。具体步骤如下:

  1. 在 Debug 选项中,选择 Attach to Process
    在这里插入图片描述
  2. 在弹出的对话框中,在红色框区域选择你想要调试的 exe 程序即可
    在这里插入图片描述
    下面介绍一下这个对话框里面的内容,在不同情况下,你需要选择不同的调试方式。
  • Connection type:一般来说你只需要选择 Default 选项即可(即本地调试),如果需要远程调试别的计算机上的进程,则需要选择 Remote,关于这种调试方式超出了本文想要撰写的范围,你可以自行去了解
  • Available porcesses:在这里选择你要调试的进程,要快速找到你想要的进程,先选中一个进程,然后直接键入你要的进程的名字即可
  • Attach to:
    - Automatically determine the type of code to debug:自动选择调试的类型
    - Debug these code types:提供了大量调试的类型,你可以根据自己的需要进行选择。一般来说,C/C++ 的调试选择 Native 选项
    【注】想要快速重新 attach 一个之前 attach 的进程,可以使用 Reattach to Process 这个选项,它位于该小节第一幅图红框的下一个选项

_DEBUG 宏与 debug 模式

在编写代码的时候,一开始,我们都是在 debug 模式下编写,这时我们会为了打印一些信息而在代码中编写输出函数,但是当放到 release 模式下时,我们肯定不能把这些输出信息给打印出来,但一方面有时这些 debug 模式下的打印信息很多,很分散,另一方面,有时需要不停在 debug 和 release 下来回切换,这就使得单纯的增加和删除输出语句很麻烦。幸好,C++ 提供了 DEBUG 宏的方式,结合条件编译语句,可以让这些打印语句只在 debug 模式下起作用。

// 在 debug 模式下执行这条语句
#ifdef _DEBUG
	cout << "调试模式" << endl;
#else
	cout << "生产模式" << endl; // 这条代码是作为对比,在实际开发过程中,当然可以不要这段代码
#endif

当然,上面的代码还可以得到进一步的优化使得更加简便,具体可以阅读我在“参考”部分给出的链接8,9。

其它技巧

快速重启 debug

在这里插入图片描述
红色框圈起的是重启调试按钮,如果你在调试的时候修改了代码,或者想要重新执行调试,那么这个按钮比停止调试再启动调试更快。

固定,值的位置

有时候我们希望直接观察变量的值,而不是移动鼠标到变量上时才能观察,就可以将它固定下来。

下图中,将鼠标移动到变量上,弹出的小框右侧有一个图书钉样式的符号,点击它即可实现效果。
在这里插入图片描述

获取变量的内存地址

在调试状态中,对某个变量选择 Add Watch 操作
在这里插入图片描述
双击红色框的部分,键入 & 符号,即可获得内存地址
在这里插入图片描述
按 【Debug】-【Windows】-【Memory】的顺序点击选择内存视图
在这里插入图片描述
复制内存地址到红框中,即可查看其在内存中的值
在这里插入图片描述

调试时变量值的进制转换

在调试的时候,有些变量值我们希望 visual studio 能以 10 进制的方式展示出来,而不是 16 进制。

如下图,storeLoc 指针指向 numDec 的地址,在 watch 窗口中,我们可以通过 numDec 的地址拿到 numDec 的值,但是这个值是以 16 进制展示的,即 0x0000000a(也就是 10)
在这里插入图片描述
要实现进制的转换非常简单,只需要右击 0x0000000a 然后取消 16 进制展示即可
在这里插入图片描述
之后就可以看到 10 进制的值了
在这里插入图片描述
【注】你可以直接在鼠标移动到编辑器中的变量上时,实现这个转换,但因为我的截图工具不能截图这种悬浮的效果,所以通过 watch 窗口展示

其他

烫烫烫与屯屯屯

在 VC 下调试的过程中,有一些没有初始化的变量,我们可以看到它的值是“烫烫烫
在这里插入图片描述
这是什么原因呢?其实,在 Debug 模式中,所有分配的栈空间的每个字节都会初始化成 0xCC 这个值(汉字编码就是烫),之所以会是这个值,主要是方便判断一个变量有没有初始化(当然,显然的是这样的方式只能作为一种参考),在【获取变量的内存地址】这一小节的第三,四幅图中,你可以看到未初始化的内存地址都是 CC 的值。

当然这和编译器有关,有些编译器选择 0xCDCDCDCD 作为未初始化的标记,这样我们就会看到“屯屯屯”的字样。

关于类似 0xCDCDCDCD 这样的值的含义,你可以通过这个链接了解到更多。

参考

  1. 零基础调试的方法
  2. 初步了解 Visual Studio 调试器
  3. 在 Visual Studio 中使用跟踪点将信息记录到“输出”窗口中
  4. Use breakpoints in the Visual Studio debugger
  5. Troubleshoot Breakpoints in the Visual Studio Debugger
  6. 《程序员的自我修养 – 链接,装载与库》
  7. 如何使用vs在调试时查看内存
  8. C/C++ 中利用debug宏定义打开/关闭调试输出
  9. C调试宏DEBUG的定义与使用
  10. 调试程序文档 :2022
  11. Debugging in Visual Studio:2015
  12. VS中的Modules窗口
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐