调试程序中包含了应用程序的符号信息。符号信息意味着跟踪(例如)未编译代码中使用的变量名,这样,它们就可以匹配已编译的机器码应用程序中现有的值。符号信息包含在.pdb文件中,这些文件位于计算机的Debug目录下。调试程序可以执行许多有用的操作,包括:

  • 给VS输出调试信息
  • 在执行应用程序的过程中,查看(并编辑)变量的值
  • 暂停程序、重新启动程序
  • 在代码的某个位置自动暂停程序的执行
  • 在一次执行中的一行代码
  • 在应用程序的执行过程中,监视变量内容的修改
    在运行期间修改变量的内容
    测试函数的调用
    7.1.1非中断(正常)模式下的调试

在Output窗口中,可以查看与代码的编译和执行相关的信息,包括在编译过程中遇到的错误等,还可以将自定义信息直接写到窗口中。
输出调试信息可以使用下列两个命令:
Debug.WriteLine()
Trace.WriteLine()
这两个命令函数的使用方法几乎完全相同,但有一个关键的区别。第一个命令仅在调试模式下运行,而第二个命令还可以用于发布程序。

注意这两个函数的用法与Console.WriteLine()是不同的。其第一个的字符串参数是用于输出消息,而不需要使用{X}语法插入变量值。这意味着必须使用+运算符在字符串中插入变量值。第二个字符串参数,用于显示输出文本的类别,这样可以区别不同地方输出类似的消息。这两个函数的一般输出格式是:
<category>:<message>

[示例]

class Program
 {
 static void Main(string[] args)
 {
 int[] testArray = { 4, 7, 4, 2, 7, 3, 7, 8, 3, 9, 1, 9 };
 int[] maxValIndices;
 int maxVal = Maxima(testArray,out maxValIndices);
 Console.WriteLine("Maximum value {0} found at element indices:",maxVal);
 foreach(int index in maxValIndices)
 {
 Console.WriteLine(index);
 }
 Console.ReadKey();
 } static int Maxima(int[] integers,out int[] indices)
 {
 Debug.WriteLine("Maximun value search started.");
 indices = new int[1];
 int maxVal = integers[0];
 indices[0] = 0;
 int count = 1;
 Debug.WriteLine("Maximum value initialized to " + maxVal + 
 ",at element index 0.");
 for(int i = 1; i < integers.Length; i++)
 {
 Debug.WriteLine("Now looking at element at index " + i + ".");
 if(integers[i] > maxVal)
 {
 maxVal = integers[i];
 count = 1;
 indices = new int[1];
 indices[0] = i;
 Debug.WriteLine("New maximun found.New value is " + maxVal +
 ", at element index " + i + ".");
 }
 else
 {
 if(integers[i] == maxVal)
 {
 count++;
 int[] oldIndices = indices;
 indices = new int[count];
 oldIndices.CopyTo(indices,0);
 indices[count - 1] = i;
 Debug.WriteLine("Duplicate maximum found at element index " + i + ".");
 }
 }
 Trace.WriteLine("Maximum value " + maxVal + "found,with " + count +
 " occurrences.");
 Debug.WriteLine("Maximum value search completed."); 
 }
 return maxVal;
 }
 }

除了Debug.WriteLine、Trace.WriteLine之外,还有其他函数可以用于输出调试信息。首先是与Console.Write()等价的函数:

  • Debug.Write()
  • Trace.Write()

这两个函数使用与WriteLine()函数相同的语法,但它们是有区别的,因为它们没有添加行尾字符。
还有下述函数:

  • Debug.WriteLineIf()
  • Trace.WriteLineIf()
  • Debug.WriteIf()
  • Trace.WriteIf()

这些函数的参数都与没有if的函数相同,但增加了一个必选参数,且该参数在列表参数的最前面。这个参数额值为布尔值(或者为计算结果为布尔值的表达式),只有这个值为true,函数才会输出文本。

1、跟踪点
把信息输出到输出窗口中的另一种方法是使用跟踪点。跟踪点是输出调试信息且不修改代码的一种方式。添加跟踪点的过程如下所示:
(1)把光标放在要插入跟踪点的代码行上。
(2)右击该行代码,选中断点|插入跟踪点。
(3)在打开的“当断点命中时”对话框中,在“打印消息”文本框中输入要输出的字符串。如果要输出变量值,应把变量名放在花括号中。
(4)单击OK。
(5)在包含跟踪点的代码行左边会出现一个红色的菱形,该行代码也会突出显示为红色。
还有一个窗口可用于快速查看应用程序中的跟踪点。要显示这个窗口,在调试状态下可用从VS菜单中选中调试选择调试|窗口|断点。这是显示断点的通用窗口。可以定制显示信息,从这个窗口的“列”下拉框选中“命中次数”,显示与跟踪点关系密切的信息。
在代码窗口右击跟踪点,或者利用断点窗口,可用删除或临时禁用跟踪点。在断点窗口中,跟踪点右边的复选框确定是否激活跟踪点;禁用的跟踪点未被选中,在代码窗口中显示为菱形框,而不是实心菱形。
输出调试信息的两种方法:
(1)诊断输出:总是要从程序中输出调试结果时使用这种方法,尤其是要输出的字符串比较复杂,涉及几个变量或许多信息的情况下,使用该方法比较好。另外,如果要在发布模式下获得执行应用程序的调试结果,Trace命令常常是唯一的选择。
(2)跟踪点:调试应用程序时,希望快速输出重要信息,以便解决语义错误,应使用跟踪点。

7.1.2 中断模式下的调试
进入中断模式的最简单方式是在运行程序时,单击VS中的暂停按钮。暂停应用程序是进入中断模式的最简单方式,但这并不能更好地控制停止程序运行的位置。一般情况下,最后使用断点。
(1)、断点
断点是源代码中自动进入中断模式的一个标记。它们可用配置为:

  • 在遇到断点时,立即进入中断模式
  • 在遇到断点时,如果布尔表达式的值为true,就进入中断模式
  • 遇到某断点一定的次数后,进入中断模式
  • 在遇到断点时,如果自从上次遇到断点以来变量的值发生了变化,就进入中断模式
  • 把文本输出到调试窗口中,或者执行一个宏

添加断点有几种方法。要添加一个断点,当遇到该断点所在的代码行时,就中断执行,可以单击该代码行左边的灰色区域;右击该行代码;选择断点|插入断点菜单项;从VS菜单中选择调试|切换断点;或者按下F9。断点在该代码行的旁边显示为一个红色的圆,而该代码也突出显示。
在断点窗口中,可用禁用断点(删除描述信息左边的记号;禁用的断点为未填充的红色圆圈来表示),删除断点,编辑断点的属性。
带有附加属性集(例如条件或遇到断点次数)的断点,在显示时略有区别,已配置的断点不是显示一个简单的红色圆圈,而是在红色的圆圈中有一个白色的加号。
(2)、进入中断模式的其他方式
进入中断模式还有两种方式。一种是在产生一个未处理的异常时选择进入该模式。另一种方式是在生成一个判定语句时中断。
判定语句是可以用用户定义的消息中断应用程序的指令。它们常常用于应用程序的开发过程,作为测试程序是否能平滑运行的一种方式。例如,在应用程序的某一处要求给定的变量值小于10,此时就可以使用一个判定语句,确定它是否为true,如果不是,就中断程序的执行。当遇到判定语句时,可以选择Abort,中断应用程序的执行,也可以选择Retry,进入中断模式,还可以选择Ignore,让程序像往常一样继续执行。
与前面的调试输出函数一样,判定函数也有两个版本:
Debug.Assert()
Trace.Assert()
这两个函数带三个参数。第一个参数是一个布尔值,其值为false会触发判定语句。第二、三个参数是两个字符串参数,分别把信息写到弹出的对话框和输出窗口。
判定语句通常在应用程序的早期使用比较有效。可以推出应用程序的一个发布程序,其中包含Trace.Assert()函数,以列出各种信息。如果触发了判定语句,用户就会收到通知,把这些消息传递给开发人员。这样,即使开发人员不知道错误是如何发生的,也可以改正这个错误。

2、监视变量的内容
查看变量值的最简单方式是在中断模式下,使鼠标指向源代码中的变量名,此时会出现一个黄色的工具提示,显示该变量的信息,其中包括该变量的当前值。
还可以高亮度显示整个表达式,以相同的方式得到该表达式的结果。对于比较复杂的值例如数组,甚至可以扩展工具提示中的值,查看各个数组元素。
在调试状态可以修改变量的值,只需在Value列中为要编辑的变量输入一个新值即可。
Watch窗口至多可以有4个,它可以监视特定变量或涉及特定变量的表达式。要使用这个窗口只需在Name列中输入变量名或表达式,就可以查看它们的结果,注意并不是应用程序中的所有变量在任何时候都可以用,并Watch窗口对变量做出标记。要在Watch窗口添加变量,还可以直接把变量从源代码拖到该窗口。
在Watch窗口中可以访问变量的各种结果,一个优点是它们可以显示变量在断点之间的变化情况。新值显示为红色而不是黑色,所有很容易看出哪个值发生了变化。
要添加更多的Watch的窗口,可以在中断模式下,使用调试|窗口|监视|监视N菜单选项打开或关闭监视的四个窗口。每个窗口都可以包含变量和表达式的一组观察结果,所有可以把相关的变量组合在一起,以便访问。
除了这些Watch窗口外,还有一个快速监视窗口,它能快速提供源代码中某个变量的详细信息。要使用这个窗口,可以右击要查看的变量,选择快速监视菜单选项。
监视窗口的一个要点是,它们可以在应用程序的各个执行过程之间保留下来。如果中断程序,再重新运行,就不必再次添加监视窗口了,VS会记住上次使用的Watch窗口。

3、单步执行代码
进入中断模式后,在代码视图的左边,正在执行的代码旁边会出现一个光标(如果使用断点进入中断模式,该指针最初应显示在断点的红色圈内)。在这个位置上,可以选择逐行执行。
Step Info --执行并移动到下一个要执行的语句上。
Step Over -- 同上,但并不进入嵌套的代码块,包括函数。

4、书签窗口和命令窗口
命令窗口和书签窗口(选择视图|其他窗口菜单)可以在运行应用程序的过程中执行命令。通过命令窗口可以手动执行VS操作(例如菜单和工具栏操作),书签窗口可以执行源代码,计算表达式,还可以执行其他代码。
这些窗口在内部是链接在一起的。甚至可以在它们之间切换:输入命令immed,可以从命名窗口切换到书签窗口,输入>cmd可以从书签窗口切换到命令窗口。
这个窗口最简单的用法是计算表达式,有的像监视窗口“one shot”。为此,只需输入一个表达式,并按回车键即可。接着就会显示请求的信息。也可以修改变量的内容。
在大多数情况下,使用前面介绍的变量监视窗口更容易得到相同的结果,但这个技巧对于常常发生变化的变量值仍很方便,也适合测试以后不感兴趣的表达式。

5、调用堆栈窗口
调用堆栈窗口描述了程序是如何执行到当前位置的。简言之,该窗口显示了当前函数、调用它的函数,以及调用函数的函数(即一个嵌套的函数调用列表)。调用的位置也被记录下来。
如果双击某一项,就会移动到相应的位置,跟踪代码执行到当前位置的过程。
在第一检测错误时,这个窗口非常有用,因为它们可以用来查看错误发生前的情况。对于常用函数中出现的错误,它有助于找到错误的源头。
【注意】
有时这个窗口会显示一些非常杂乱的信息,例如有时因为以错误的方式使用了外部函数,错误在应用程序的外部发生,就会出现这种情况。如果需要,可以右击该窗口,选择外部代码,查看外部引用。