中断模式下的调试
调试技术的剩余内容在中断模式下工作。可以通过几种方式进入这种模式,这些方式都会以某种方式暂停程序的执行。
1. 进入中断模式
进入中断模式的最简单方式是在运行应用程序时,单击IDE中的 全部中断
按钮。这个 全部中断
按钮在 调试
工具栏上,应把该工具栏添加到VS默认显示的工具栏中。为此,右击工具栏区域,然后选择 调试
,这个工具栏 如图 7-6 所示
。
图 7-6
在这个工具栏上,前 3 个按钮可以手工控制中断。在 图 7-6
上,它们显示为灰色,因为在程序没有运行时,它们是不能工作的。在后面的章节需要其他按钮时,再介绍它们。
运行一个应用程序时,工具栏就 如图 7-7 所示
。
图 7-7
现在,就可以使用之前显示为灰色的 3 个按钮了。它们可以:
● 暂停应用程序的执行,进入中断模式
● 完全停止应用程序的执行(不进入中断模式,而是退出应用程序)
● 重新启动应用程序
暂停应用程序是进入中断模式的最简单方式,但这并不能更好地控制停止程序运行的位置。我们可能会停止在应用程序正常暂停的地方,例如,要求用户输入信息。还可以在长时间的操作或循环过程中进入中断模式,但停止的位置可能相当随机。一般情况下,最好使用断点。
断点
断点是源代码中自动进入中断模式的一个标记。它们可以配置为:
● 在遇到断点时,立即进入中断模式
● 在遇到断点时,如果布尔表达式的值为 true
,就进入中断模式
● 遇到某断点一定的次数后,进入中断模式
● 在遇到断点时,如果自从上次遇到断点以来变量的值发生了变化,就进入中断模式
● 把文本输出到 输出
窗口中,或者执行一个宏(参见 “跟踪点”小节)
注意 ⚠️,上述功能仅能用于调试程序。如果编译发布程序,将忽略所有断点。
添加断点有几种方法。要添加简单断点,当遇到该断点所在的代码行时,就中断执行,可以单击该代码行左边的灰色区域。其他方法包括:右击该行代码行,选择 断点 | 插入断点
菜单项;或者按下 F9
键。
断点在代码行的旁边显示为一个红色圆圈,而该行代码也突出显示,如图 7-8 所示
。
图 7-8
使用 Breakpoints
窗口还可以查看文件中的断点信息(前面介绍过启用该窗口的方法)。在 Breakpoints
窗口中,可以禁用断点(删除描述信息左边的记号;禁用的断点用未填充的红色圆圈来表示),删除断点,编辑断点的属性。还可以为断点添加标签,这是分组选定断点的一种便捷方式。可以在 Labels
列中查看标签,以及按标签筛选 Breakpoints
窗口中的项。
这个窗口中显示的 条件
和 命中次数
菜单项,就可以编辑它们。
选择 条件
将弹出一个对话框。在该对话框中可以键入任意布尔表达式,该表达式可以包含在断点位置仍在作用域内的任何变量。例如,可以配置一个断点,输入表达式 maxVal > 4
,选择 Is true
选项,则在遇到这个断点,且 maxVal
的值大于 4 时,就会触发该断点。还可以检查这个表达式时否有变化,仅当发生变化时,才会出发断点(例如,如果在遇到断点时,maxVal
的值从 2 改为 6,就会触发该断点)。
选择 命中次数
将弹出另一个对话框。在这个对话框中可以指定在遇到断点多少次后才触发该断点。该对话框中的下拉列表提供了如下选项:
● 总是中断
● 中断,条件是命中次数等于
● 中断,条件时命中次数几倍于
● 中断,条件是命中次数大于或等于
所选的选项与选项旁边的文本框中输入的值共同确定断点的行为。这个计数在比较长的循环中很有用,例如,在执行了前 5000 次循环后需要中断。如果不这么做,中断并再启动 5000 次是很痛苦的。
带有附加属性集(例如,条件或遇到断点的次数)的断点在显示时与普通断点略有区别。已配置的断点不是显示一个简单的红色圆圈,而在红色的圆圈中有一个白色加好。这是很有用的,因为它允许很快辨认出哪个断点总是进入中断模式,哪个断点仅在某种情况下才进入中断模式。
进入中断模式的其他方式
进入中断模式还有两种方式。一种是在抛出一个未处理的异常时选择进入该模式。这种方式在本章后面讨论到错误处理时论述。另一种方式是在生成一个判定语句时中断。
判定语句是可以用用户定义的消息中断应用程序的指令。它们常常用于应用程序的开发过程,作为测试程序时否能平滑运行的一种方式。例如,在应用程序的某一处要求给定的变量值小于 10,此时就可以使用一个判定语句,确定它是否为 true
,如果不是,就中断程序的执行。当遇到判定语句时,可以选择 终止
,中断应用程序的执行,也可以选择 重试
,进入中断模式,还可以选择 忽略
,让应用程序像往常一样继续执行。
与前面的调试输出函数一样,判定函数也有两个版本:
● Debug.Assert()
● Trace.Assert()
其调试版本也是仅用于编译调试程序。
这两个函数带 3 个参数。第一个参数是一个布尔值,其值为 false
会触发判定语句。第二、三个参数是两个字符串,分别把信息写到弹出的对话框和 输出
窗口中。上面的示例需要一个函数调用,如下所示:
Debug.Assert(myVar < 10, "myVar is 10 or greater.", "Assertion occurred in Main().");
判定语句通常在应用程序的早期使用比较有效。可以分发应用程序的一个发布程序,其中包含 Trace.Assert()
函数,以了解应用程序的运行情况。如果触发了判定语句,用户就会收到通知,把这些消息传递给开发人员。这样,即使开发人员不知道错误是如何发生的,也可以改正这个错误。
例如,在第一个字符串中提供有关错误的简短描述,在第二个字符串中提供下一步该如何操作的指示:
Trace.Assert(myVar < 10, "Variable out of bounds.",
"Please contact vendor with the error code KCW001.");
如果出发了这个判定语句,用户就会看到 如图 7-9
所示的对话框。
诚然,这并不是最友好的对话框,因为它包含了许多令人感到迷惑的信息,但如果用户给开发人员发送了错误的屏幕图,开发人员就可以很快找出问题所在。
下一个要论述的主题是应用程序中断,以及进入中断模式后,我们可以做什么。一般情况下,进入中断模式的目的是找出代码中的错误(或确信程序工作正常)。一旦进入中断模式,就可以使用各种技巧分析代码,并分析应用程序在暂停时的状态。
图 7-9
2. 监视变量的内容
监视变量的内容是VS帮助我们使工作变得简单的一个例子。查看变量值的最简单方式是在中断模式下,使鼠标指向源代码中的变量名,此时就会出现一个工具提示,显示该变量的信息,其中包括该变量的当前值。
还可以高亮显示整个表达式,以相同方式得到该表达式的结果。对于比较复杂的值(例如数组),甚至可以扩展工具提示中的值,查看各个数组元素项。甚至可以把这些工具提示窗口固定到代码视图中,这对于查看特别感兴趣的变量很有帮助。固定的工具提示中添加注释,移动工具提示窗口,查看变量的最后一个值,即使应用程序并没有运行也同样如此。
注意 ⚠️,在运行应用程序时,IDE中各个窗口的布局发生了变化。在默认情况下,运行期间会发生如下变化(变化的情况会根据具体的安装略有区别):
● 属性窗口和其他一些窗口会消失,其中可能包括 解决方法资源管理器
窗口
● 错误列表窗口会被IDE窗口底部的两个新窗口替代
● 新窗口中会出现几个新的选项卡
新的屏幕布局 如图 7-10 所示
。这可能与读者的显示情况不完全相同,一些选项卡和窗口可能不完全匹配。但是,这些窗口的功能(后面将讨论)是相同的,这个显示完全可以通过 视图
和 调试
| 窗口
菜单来定制(在中断模式下),也可以在屏幕上拖动窗口,重新设定它们的位置。
左下角的新窗口在调试时非常有用,它允许在中断模式下,密切监视应用程序的变量值。它包含 3 个选项卡,如下所示:
● 自动窗口 — 当前和前面的语句使用的变量( Ctrl + D, A
)
● 局部变量 — 在作用域内的所有变量( Ctrl + D, L
)
● 监视 N — 可定制的变量和表达式显示(其中 N 从 1~4,在 调试
| 窗口
| 监视
上 )
图 7-10
这些选项卡的工作方式或多或少有些类似,并根据它们的特定功能添加了各种附加特性。一般情况下,每个选项卡都包含一个变量列表,其中包括变量的名称、值和类型等信息。更复杂的变量(如数组)可以使用变量名左边的 +
和 -
(展开/折叠)符号进一步查看,它们的内容可以树状视图的方式显示。例如,在前面的示例中,在代码中放置了一个断点,得到的 局部变量
选项卡 如图 7-11所示
,其中显示了数组变量 Integres
的展开视图。
图 7-11
在这个视图中,还可以编辑变量的内容。它有效地绕过了前面代码中的其他变量赋值。为此,只需在 Value
列中为要编辑的变量输入一个新值即可。也可以将这种技巧用于其他情况,例如,需要修改代码才能编辑变量值的情况。
可通过 监视
窗口监视特定变量或涉及特定变量的表达式。要使用这个窗口,只需在 Name
列中键入变量名或表达式,就可以查看它们的结果。注意,并不是应用程序中的所有变量在任何时候都在作用域内,并在 监视
窗口中对变量做出标记。例如,图 7-12
显示了一个 监视
窗口,其中包含几个示例变量和表达式,在遇到 Maxima()
函数末尾前面的一个断点时,会显示这个 监视
窗口。
图 7-12
testArray
数组对于 Main()
来说是局部数组,所以在该图中没有值,而是显示了一个信息,告诉我们这个变量不在作用域内。
要在监视
窗口中添加变量,还可以把变量从源代码拖动到该窗口中。
在这个窗口中可以访问变量的各种显示结果,一个优点是它们可以显示变量在断点之间的变化情况。新值显示为红色而不是黑色,所以很容易看出哪个值发生了变化。
如前说述,要添加更多 监视
窗口,可以在中断模式下,使用 调试
| 窗口
| 监视
菜单选项打开或关闭 监视
的 4 个窗口。每个窗口都可以包含变量和表达式的一组观察结果,所以可以把相关的变量组合在一起,以便于访问。
除了这些 监视
窗口外,VS还有一个 快速监视
窗口,它能快速提供源代码中某个变量的详细信息。要使用这个窗口,可以右击要查看的变量,选择 快速监视
菜单选项。但大多数情况下,使用标准的 监视窗口
或者像前面介绍的那样固定变量的工具提示就足够了。
监视窗口
可以在应用程序的各个执行过程之间保留下来。如果中断应用程序,再重新运行,就不必再次添加 监视窗口
了,IDE会记住上次使用的 监视窗口
。
3. 单步执行代码
前面介绍了如何在中断模式下查看应用程序的运行情况,下面讨论如何在中断模式下使用IDE单步执行代码,查看代码的准确执行结果,人们的思维速度不会比计算机运行得更快,所以这是一个极有价值的技巧。
VS进入中断模式后,在代码视图的左边,马上要执行的代码旁边会出现一个黄色箭头光标(如果使用断点进入中断模式,该光标最初应显示在断点的红色圆圈中),如图 7-13 所示
。
图 7-13
这显示了在进入中断模式时程序执行到的位置。在这个位置上,可以选择逐行执行。为此,使用前面看到的其他一些 调试
工具栏按钮,如图 7-14 所示
。
图 7-14
第 6、7、8 个图标控制了中断模式下的程序流。它们依次是:
● 逐语句 — 执行并移动到下一条要执行的语句上
● 逐过程 — 同上,但不进去嵌套的代码块,包括函数
● 跳出 — 执行到代码块的末尾处,在执行完该语句块后,重新进入中断模式
如果要查看应用程序执行的每个操作,可以使用 逐语句
按顺序执行指令,这包括在函数中执行,如上面示例中的 Maxima()
。当光标到达第 15 行,调用 Maxima()
时,单击这个图标,会使光标移动到 Maxima()
时,单击这个图标,会使光标移动到 Maxima()
函数内部的第一行代码上。而如果光标移动到第 15 行时单击 逐过程
,就会使光标移动到第 16 行,不进入 Maxima()
中的代码(但仍执行这段代码)。如果单步执行到不敢兴趣的函数,可以点击 跳出
,返回到调用该函数的代码。在单步执行代码时,变量的值可能会发生变化。注意观察上一节讨论的 监视
窗口,可以看到变量值的变化情况。
通过右击代码行并选择 设置下一语句(X)
,或将黄色箭头拖动到不同的代码行,也可以更改接下来要执行的代码行。这有时是不可行的,例如跳过变量初始化时。但是,当跳过存在问题的代码行来查看发生的情况时,或向后移动箭头来重复执行代码时,这种方法是非常有用的。
在存在语义错误的代码中,这些技巧也许是最有效的。可以单步执行代码,当执行到有错误的代码时,错误会像正常运行程序那样发生。或者可以修改执行代码,让语句多次执行。在这个过程中,可以监视数据,看看什么地方出错。本章后面将使用这个技巧查看示例应用程序的执行情况。
4. 即时窗口和命令窗口
命令窗口
和 即时窗口
(在 调试
窗口菜单下)可以在运行应用程序的过程中执行命令。通过 命令窗口
可以手动执行VS操作(例如,菜单和工具栏操作),即时窗口
可以执行与当前正在执行的源代码不同的额外代码,以及计算表达式。
VS中的这些窗口在内部是链接在一起的(实际上,VS的早期版本把它们当作同一个窗口)。甚至可以在它们之间切换:输入命令 immed
,可以从 命令窗口
切换到 即时窗口
;输入 cmd
可以从 即时窗口
切换到 命令窗口
。
下面详细讨论 即时窗口
,因为 命令窗口
仅适用于复杂的操作。即时窗口
最简单的用法是计算表达式,有点像 监视
窗口中的一次性使用。为此,只需键入一个表达式,并按回车键即可。接着就会显示请求的信息,如图 7-15 所示
。
图 7-15
可以在这里修改变量的内容,如图 7-16 所示
。
图 7-16
在大多数情况下,使用前面介绍的变量监视窗口更容易得到相同的效果,但这个技巧对于调整变量值和测试表达式很方便。
5. 调用堆栈窗口
这是最后一个要讨论的窗口,它描述了程序是如何执行到当前位置的。简言之,该窗口显示了当前函数、调用它的函数以及调用该函数的函数(即一个嵌套的函数调用列表)。调用的确切位置也被记录下来。
在前面的示例中,在执行到 Maxima()
时进入中断模式,或者使用代码单步执行功能移动到这个函数的内部,得到 如图 7-17
所示的信息。
图 7-17
如果双击某一项,就会移动到相应的位置,跟踪代码执行到当前位置的过程。在第一次检测错误时,这个窗口非常有用,因为它们可以查看临近发生时的情况。对于常用函数中出现的错误,则有助于找到错误的源头。
有时调用堆栈
窗口会显示一些非常杂乱的信息,例如,有时因为以错误方式使用了外部函数,错误在应用程序的外部发生,就会出现这种情况。此时,这个窗口中会列出一个很长的列表,其中只有一两项是我们熟悉的。如有必要,可以右击该窗口,选择转到源代码
,来查看外部引用。