调试是纠正或修改代码,使之能够顺利地编译、运行的过程。为此,VC IDE提供了功能强大的调试和跟踪工具。php
开发环境老是为你的工程建立调试版和发行版。在调试版里,咱们排查各类可能的程序错误,而后制做成发行版以得到较好的信息。这就是调试版与发行版的区别:前者包含了较多调试信息,最终执行文件较大,性能较差;后者最终执行文件较小,性能更好。具体地讲,发行版和调试版区别有:express
1. 调试版下,可使用诊断和跟踪宏,如ASSERT和TRACE,MFC类的DUMP功能也定义在调试版下。数组
2. 额外的变量初始化。编译器会自动将你未初始化的变量逐每字节赋值为0xCC。sass
3. 内存分配监视。在调试版下,在堆上分配的内存都会记录,作额外的初始化(0xCD),在释放时,会将其内容逐字节置0xFD。并发
怎样配置调试版[C1] 或发行版?ide
调试版和发行版均可以从 Build | Configurations中增长,或者当这两个配置已存在的状况下,从Project | Settings命令下进行配置。二者在配置上主要有如下不一样:函数
1. 两个版本应该配置成不一样的输出文件夹。这在Project | Settings | General中的两个编辑框中设定。工具
2. 调试版下,程序应该连接Debug版的c运行时库、禁止代码优化,并在C++属性页中的Preprocessor类别中加上预约义标识符_DEBUG。在Link属性页中的Debug类别中,选中DebugInfo和Microsoft Format选框。post
4. 发行版下,程序应该连接Release版的c运行时库、设置代码优化,不定义_DEBUG标识符,去掉DebugInfo选框中的选择。性能
VC6.0的编译器能够报告大约1100个左右的错误,这还不包含警告。而在编译和调试过程当中咱们还不只仅遇到编译错误,也可能遇到连接错误和其余错误,所以咱们只能讲一些很常见、很重要的编译错误。对咱们没有列出来的编译错误,解决它的最好办法是,在output窗口中将光标定位在输出行,而后按F1键以得到MSDN的帮助。若是MSDN的版本与你的VC不兼容,那么你能够用得到到Error xxxx做为关键字在MSDN中搜索。目前这些错误的说明帮助出如今Visual C++ Programmer's Guide中的Build Errors条目下。
并不是错误
形如“Loaded 'C:\WINNT\system32\gdi32.dll', no matching symbolic information found.”这样的报告不是错误。通常状况下,咱们只须要跟踪本身的源代码,检查在那里发生了什么错误。但有时候咱们也想看看,若是一个错误报告在系统DLL里时到底是怎么回事。Microsoft不肯意在系统DLL中包含调试信息,所以它提供调试符号帮助你跟踪。随VC6.0发行的有一个调试符号包,但其内容可能不能跟得上你操做系统版本。若是你须要适合你操做系统版本的调试符号,请到微软更新网站上下载。
l LNK2001错误
LNK2001错误报告一个不能识别的外部标识符。一般你使用一个函数或外部变量,都必须事先给出其声明,不然就会产生这样一个错误。能够从如下几个方面寻找缘由:
1. 使用内联函数,但该内联函数定义在.cpp文件中,而不是在.h文件中。
2. 声明了函数或外部变量,但找不到它们的定义。
3. 程序入口设置错误,而且进一步提示_WinMain@16': Unresolved External Symbol或相似错误。在Windows程序中,程序并非最早从WinMain()或Main()这样的函数处开始执行,而是从WinMainCRTStartup()或MainCRTStratup函数处开始执行。这四个函数(其实是8个,每一个函数都有UNICODE版和单字节版)必须配套。若是进入点是WinMainCRTStartup (),则它期待在你的程序中找到一个WinMain()函数,若是找不到,则发生上述错误。
4. 在程序中使用了线程启动函数_beginthread,但选择的连接库为LIBC.LIB。
其余错误见MSDN。
编译错误都是静态的,更多的错误发生在运行时。为了排除运行时的错误,首先须要的是可以使正在运行的代码停下来。VC6.0提供了多种断点设置。
l 位置断点
l 条件断点:能够设置如下条件为真时中断程序
1. 单变量值改变时
2. 单变量值判断条件中断,如K>0,i == 5等
3. 数组单元素或多元素值改变或值判断条件中断。
4. 指针:指针变量改变或指针所指内容改变
5. 监视固定内存值变化
6. 监视寄存器中值的变化,如 cs == 0等。
也能够直接输入断点设置表达式,其语法是:
①Advanced Syntax 详解
{[function],[source],[exe] } location
{[function],[source],[exe] } variable_name
{[function],[source],[exe] } expression
括在{}对中的是上下文设置,这三项中任意一项均可省略,但省略前面的项时,应注意保留逗号。以下面的设置
是错误的:
{File.c, File.exe} .143 - Bad
正确的设置如
{Fun} .143 这样在函数Fun的第143行设置了一个断点。
更详细的说明见下面的举例:
{[function],[source],[exe] } .100 - A line number (this may not work with some languages)
{[function],[source],[exe] } @100 - A line number (this works for all languages)
{[function],[source],[exe] } Traverse - A function name
{[function],[source],[exe] } CMyWindow::OnCall - A function name
{[function],[source],[exe] } 00406030 - A memory address (decimal)
{[function],[source],[exe] } 0×1002A - A memory address (hexadecimal)
{function,[source],[exe] } Label - A statement label. The context must include function since
Label is visible in the function’s scope.
更详细的帮助,查看Visual C++ Programer’s Guid中Setting Breakpoints When Values Change or Become True主题。
l 消息断点
l 异常断点
当程序发生异常时,咱们应该当即停下来,看看发生了什么事情。默认地,当一个异常发生时,Debugger 将异常信息写到Output 窗口中,并根据你的选择决定是否当即中断程序。这个设置在Debug菜单中的Exceptions选项中提供。这里提供了两个选项Stop Always和Stop If Not Handled。当Stop If Not Handled被选中时,debugger仅仅往Output窗口中输出一条信息,而并不停止程序,除非没有这个异常最后也得不处处理。在有些状况下,这会是一个有些晚的时机。当Stop Always被选中时,debugger会在异常处理代码接管之前就中断程序。
开发环境提供了一系列的查看方式来临视甚至修改程序的运行状态。总共提供了六种查看窗口:
l Watch窗口
Watch窗口用来查看和解码中间变量,并容许你修改变量的值。Watch窗口共有四个Tab。每一个Tab都是一个ListView,由两栏组成。左边一栏为变量名,右边一栏则为变量取值。只要在变量名栏输入变量名并回车,就能够获得当前变量的值。下面是一些特殊的用法:
1. 列出数组中前n个元素的值:arr,n
2. 查看UNICODE编码的字符串值:str,su
3. 已知消息值,查对应的宏:messageValue,wm
4. 查看窗口类标志(Window class flag):flagValue,wc
5. 查看当前线程最后错误报告:@err,hr
6. 查看COM库最后错误报告:@hres,hr
7. 查看当前寄存器值,把寄存器名看成变量名输入就能够了。
直接在右边栏输入新的取值,就会修改该变量的取值并发生做用。
l Variables窗口
这个窗口始终跟踪当前上下文中重要的变量取值。但不能增长对变量的跟踪。它包含三个Tab:
Auto窗口跟踪当前声明或前一个声明中使用的变量,当你从一个函数调用中跳出时,还能够显示函数的返回值。
Locals窗口显示对当前函数来讲是局部的变量的取值。
This窗口显示this指针所指对象取值。
此外,在Variables窗口的上部,还有一个Context的组合框,能够用来切换当前查看的上下文。
l CallStack窗口
这个窗口显示了函数调用的状况。当你的程序出现一个断言时,你能够用这个窗口把函数调用顺序调出来,而且能够知道调用时传递参数的状况。双击窗口中列出的函数,能够定位到源代码中。
l Memory窗口
Memory窗口的上部是一个地址输入框。你能够在这里输入0×00400000,由于这时进程的起始点,因此绝不奇怪,你会看到MZ这两个字。Memory窗口支持定制显示格式,并支持表达式。
在Memory窗口的下面窗口处按右键,会出现一个菜单,容许你按单字节、双字节十六进制或四字节十六进制来显示内存的内容。
若是你愿意麻烦一点,还有办法获得更准确的显示。点Tools | Options,选择Debug属性页,在这里能够决定以什么形式显示内存内容以及从什么地址开始显示。这里提供了14种格式选项。此外,你还能够输入一个地址表达式。好比,若是要知道当前栈顶的状况,在这里输入ESP。
l Disassembly窗口
这个窗口能够用跟踪优化代码,或者是复杂表达式。
l Registers窗口
Registers窗口显示全部寄存器值,并容许你修改。它与Watch窗口的区别是,你不须要记住寄存器名,它老是把全部寄存器值给你列出来。
VC提供了两个断言宏:ASSERT和VERIFY。二者的区别是,在发行版中,ASSERT行不会执行,由于在发行版中,ASSERT宏被定义成
#define ASSERT /##/
这样这行代码被注释掉,从而不会被编译。VERIFY宏在发行版中被定义成
#define VERIFY
这样VERIFY后面的代码仍然加入编译,从而获得执行。
在你掌握了这两个断言的区别后,咱们将只讲述ASSERT宏。ASSERT是一个带参宏,其参数容许是一个表达式。当表达式的值为假时,程序发生中断并提示你是否进行调试。
除了使用条件判断表达式外,ASSERT(0)也颇有用。它始终引发一个断言错,并提示你是否调试。你能够把它放在一些你不指望程序运行到达的路径上,若是事情一旦发生,则能够直接进行调试。
VC还提供TRACE宏,以及它的姊妹宏TRACE0,TRACE1,TRACE2,TRACE3。TRACE宏的语法相似于printf,包括格式字符串的规定也是一致的。后面三个宏限定你使用一个格式字符串和一至三个变量。TRACE输出到Output窗口,若是工具MFC TRACER中设置为容许的话。
MFC还提供了对象诊断工具,即名为Dump的函数。MFC库中每一个从CObject继承的类都有Dump的能力。若是你从MFC类库中派生了某个类,在有必要的状况下,最好重载这个函数。要记住,你重载的函数应该调用基类的Dump函数。另外,Dump函数只在调试版下可用。所以重载的函数也应该括在#ifdef _DEBUG宏中。