VC++中的一些调试方面的问题

一、Visual C++编辑器的“设置”菜单

当你打开或新建一个包含至少一个工程的Workspace后,Visual C++Project菜单中的“Settings…”命令就变为有效,选择它或者按下热键Alt+F7后,便可调出工程设置对话框,这里面的选项将影响整个工程的建立和调试过程,因此很重要。

在这个对话框中,左上方的下拉列表框用于选择一种工程配置,包括有Win32 DebugWin32 ReleaseAll Configurations(指前两种配置一起),某些选项在不同的工程配置中有不同的缺省值。左边的树形视图给出了当前工程所有的文件及分类情况。下面我们就以Win32 Debug为例来看看与工程有关的的四个主要选项卡的各自功能与含义(一共有十个选项卡):

1 General选项卡

这个选项卡比较简单,从上向下的第一个选项用于更改使用MFC类库的方式: DLL的方式或是静态连接。我们可以在两种方式之间进行切换。第二个选项用于指定在编译连接过程中生成的中间文件和输出文件的存放目录,对于调试版本来说,缺省的目录是工程下面的“Debug”子目录。第三个选项用于指定是否允许每种工程配置都有自己的文件依赖关系(主要指头文件),由于绝大多数工程的调试版本和发布版本都具有相同的文件依赖关系,所以通常不需要更改该选项。

2 Debug选项卡

Debug选项卡中是一些与调试有关的选项,由于选项比较多,它们被分成了几个类,我们可以从Category中选择不同的类别,选项卡就会切换显示出相应的选项。

General类别中,可以指定要调试的可执行文件名。另外三个选项可以指定用于调试的工作目录,开始调试时给程序传送的命令行参数,以及进行远程调试时可执行文件的路径。

3C/C++选项卡

C/C++选项卡控制着Visual C++的编译器,其中的选项比较多。下面有一个Project Options编辑框,里面列出的各种命令开关将会在开始编译时作为命令行参数传送给Visual C++的编译器。这些命令开关会跟随其它选项改变而改变。

General类别中,Warning level用于指定编译器显示警告的级别,如果选中了Warnings as errors,那么显示的每一个警告都将会引起一个错误,这样在编译完毕后就无法启动连接器来进行连接。Optimizations用于设置代码优化方式,优化的目的主要有提高运行速度和减小程序体积两种,但有时候这两种目的是相互矛盾的。另外,在极少数情况下,不进行优化,程序能正常运行,打开了优化措施之后,程序却会出现一些莫名其妙的问题。其实这多半是程序中有潜在的错误,关闭优化措施往往只是暂时解决问题。Debug info用于指定编译器产生的调试信息的类型,为了使用Visual C++的即编即调功能,必须在这里选择生成“Program Database for Edit and Continue”类型的调试信息。Preprocessor definitions是一些预先定义的宏名。

C++ Language类别中的选项涉及到了C++语言的一些高级特性,包括有成员指针的表示方式、异常处理、运行时类型信息,一般情况下都不用改变它们。Code Generation类别中的选项涉及如何生成目标代码,一般情况下保持缺省值即可。在Customize类别中,从上到下六个选项的含义分别为:是否禁止使用MicrosoftC++的扩展;是否允许函数级别的连接;是否消除重复的字符串;是否允许进行最小化的重建;是否允许递增编译方式;是否允许编译器在开始运行时向Output窗口中输出自己的版本信息。

Listing Files类别中,我们可以指定编译器生成浏览信息和列表文件(Listing file),前者可由浏览信息维护工具BSCMAKE生成浏览信息文件,后者则包含了C/C++源文件经过编译后对应的汇编指令。Optimizations类别允许我们对优化措施进行更细微的控制,选择了Customize后,便可以选择进行哪几项优化,在Inline function expansion中我们可以指定对内联函数的扩展方式。Precompiled Headers类别中是关于预编译头文件的一些选项,一般情况下都不用更改。Preprocessor类别中是关于预处理的一些选择。

4Link 选项卡

Link选项卡控制着Visual C++的连接器。在General类别中,可以指定输出的文件名,以及一些在连接过程中需要使用的额外的库文件或目标文件,下边五个选项的含义分别为:生成调试信息;忽略所有缺省的库文件;允许递增连接方式(这种方式可以加快连接的速度);生成MAP文件;允许进行性能分析。在Customize中选中Use program database允许使用程序数据库。在Debug类别中,我们可以指定调试信息的类别是Microsoft的格式,还是COFF格式,或者两种都有,选中Separate types后连接器会把调试信息分开放在PDB文件中,这样连接起来会更快一些,但调试时速度却会慢一些。Input类别中是一些与输入库文件有关的选项,我们可以在这里指定使用或不使用某些库文件或目标文件。Output类别中则是一些与最终输出的可执行文件有关的选项,一般情况下都不用改变。

二、Visual C++调试工具

1、调试窗口

       1)观察窗口(Watch)

       调试程序时,可使用观察窗口监视变量和表达式。

       2)快速查看窗口(Quick watch)

       功能和观察窗口差不多。

       3)变量窗口(Variables)

       变量窗口有三个标签:Auto标签显示了当前语句和前一条语句用到的变量,Locals标签显示当前函数的局部变量,this标签显示了this指针执行的对象。

       4)寄存器窗口(Register)

       可以监视CPU的寄存器、标志值以及浮点堆栈

       5)内存窗口(Memory)

       可显示从一特定地址开始的虚拟内存。Address框允许你指定从哪个虚拟内存地址开始显示。

       6)调用栈窗口(Call stack)

       可显示引起当前源代码语句执行的一系列函数调用,当前函数在堆栈的顶端。

       7)反汇编窗口(Disassembly)

       可查看编译器生成的对应于源代码的汇编指令。

2、调试符号

       程序数据库文件(.pdb)包含了Visual C++调试器所需的调试信息和程序信息。调试信息包含了变量的名字和类型、函数原型、源代码行号、类和结构的布局、FPO调试信息(重建堆栈帧)以及进行增量链接所需的信息。对于设置了Program Database for Edit and Continue选项的程序,PDB还要包含执行编辑继续功能所需的信息。

3、使用断点

       断点(BreakPoint)是运行你向调试器描述环境,并让调试器设置好程序状态的一种机制。如果没有断点,只有在程序里一步一步跟踪使用调试器。在Visual C++中,你可以设置三种类型的断点:代码定位断点、数据断点和消息断点。

三、提高调试器的查错能力

       尽量采用编译时刻检查而不是运行时刻检查。

1、使用最高的编译警告级别/W4

       if(x=2)这样的语句,默认的警告级别为/W3时不显示任何信息,但改成最高警告级别/W4时则会出现“waning C4706:assignment within conditional expression”的警告。/W4能给出一些/W3所不能给的警告。

2、在调试版本中使用/GZ编译选项

       /GZ选项用来发现那些在发布版本里才发现的错误,包括未被初始化的自动(局部)变量、堆栈错误、不正确的函数原型等。

3、使用#pragma warning编译器指示

       你可以使用#pragma warning编译器指示来禁止整个程序、特定的头文件、特定的代码文件或是特定的某一行代码的特定警告,这看你把#pragma放在哪里。

4、使用没有警告的编译法则/WX

       这个编译选项把所有的警告当成错误来对待,只有在假警告被消除之后才能应用。有时编译警告可能是合理的,处理编译警告的核心是要发现错误,而不是抑制警告本身。这个法则对于大的程序开发小组来说很有帮助。最终目标是消除错误,而不是消除警告。

四、内存空间与分配

       1、内存分配错误

       动态内存分配错误有两种基本类型:内存错误和内存泄漏。

1)内存错误

当一个指针或者该指针所指向的内存单元成为无效单元,或者内存中分配的数据结构被破坏时,就会造成内存错误。指针未被初始化,指针被初始化为一个无效地址,指针被不小心错误地修改,在与指针相关联的内存区域被释放后使用该指针(这种指针被称为虚悬(dangling)指针),这些都会使指针变为无效指针。当通过一个错误指针或者虚悬指针对内存进行写入,或者将指针强制转换为不匹配的数据结构,又或者是写数据越界,内存自身也会遭到破坏。删除未被初始化的指针、删除非堆指针、多次删除同一指针或者覆盖一个指针的内部数据结构,都会造成内存分配系统错误。

2)内存泄漏

内存泄漏在被动态分配的内存没有被释放时产生。有许多情况会导致内存泄漏,如没有在程序的全部执行路径中释放内存,没有在析构函数中释放所有的内存等。一个程序在崩溃之前可运行的时间越长,则导致崩溃的原因与内存泄漏的关系越大。

Windows会在程序结束的时候将泄漏的内存收回,因此内存泄漏是个暂时性的问题。但为什么必须消除内存泄露呢?首先,内存泄漏往往会导致系统资源的泄漏。动态分配内存往往不仅仅代表一块存储区域,还代表了某些类型的系统资源,如文件、窗口、设备上下文、GDI对象等。其次,高质量的程序和特定的服务器程序必须能够无限地运行下去。最后,内存泄漏往往是其他程序错误或不良编程习惯的征兆。

导致内参泄漏的原因:忘记释放内存;构造函数失败;存在内存泄漏的析构函数;存在内存泄漏的异常处理程序;多个返回语句;使用错误形式的delete

2、关于内存的初始化

       在调试版本里,堆里未被初始化的内存被0xCD字节模式填充,堆里释放的内存被0xDD字节模式填充。堆栈里被初始化的内存被0xCC字节模式填充。调试版本和发布版本里,未被初始化的全局内存都被初始化为0

3、内存虚拟地址空间

       Windows使用一组固定的范围来分割进程的4GB虚拟地址空间,因此有时可通过查看指针的返回值来判断指针是否有效。

1Windows2000虚拟地址空间划分

0~0XFFFF(64KB):不能用来检测空指针赋值(访问冲突)

0x10000(64KB)~0x7FFEFFFF(2GB-64KB)Win32进程私有的(非保留的),用于程序代码和数据

0x7FFF0000(2GB-64KB)~0x7FFFFFFF(2GB):不能用来防止覆盖OS分区(访问冲突)

0x800000000(2GB)~0xFFFFFFFF(4GB):为操作系统保留,不可访问(访问冲突)

2Windows2000虚拟地址空间使用

0x00030000~0x0012FFFF:线程栈

0x00130000~0x003FFFFF:堆(有时堆位于此处)

0x00400000~0x005FFFFF:可执行代码

0x00600000~0x0FFFFFFF:堆(有时堆位于此处)

0x10000000~0x5FFFFFFFApp DLLsMsvcrt.dllMfc42.dll

0x77000000~0xFFFFFFFFAdvapi32.dllComctl32.dllGdi32.dllKernel32.dllNtdll.dllRpcrt4.dllShell32.dllUser32.dll

其中,0x00400000是所有版本的Windows能使用的最低基地址。

五、一些调试技术

1、调试死循环

       使用Debug菜单下的Break命令。在Windows2000中,如果程序有输入请求,可以使用F12键中断程序,然后检查窗口的调用栈,或单步跟踪代码找到死循环的发生原因。

2、用Spy++调试与消息有关的问题

       调试消息的最好方案是使用Visual C++提供的Spy++工具。Spy++允许程序员查看窗口、消息、进程和线程。Spy++默认的消息输出:第一栏显示行号。第二栏显示接受消息的句柄。第三栏中的“S”表示消息是用SendMessage发出的,“P”代表消息是由PostMessage发出的,“R”是消息句柄的返回值。第四栏给出解码后的消息名,消息参数或返回值。

3、非常规方法

1)重新编连你的应用程序

       当你的程序表现出异常的或意外的行为,或者Visual C++编译器因为一个内部编译器错误而失败时,最好删除工程中的DebugRelease文件夹,从头开始重新进行编连。

2)重新启动Visual C++

       Visual C++有超强的能力,但编译器的某些特性也会引起奇怪的错误。如果你的程序表现得很奇怪,你可是试着清除所有的断点,关闭或隐藏观察窗口,检查工程设置对话框看最近做了什么修改,直至重新启动Visual C++以便消除由于Visual C++环境引起的异常行为。

3)重新启动Windows

       当你发现Windows或者其他程序表现出异常的或出人意料的行为时,就应该重新启动Windows,以消除操作系统给调试带来的干扰。

你可能感兴趣的:(数据结构,windows,Microsoft,vc++,编译器,preprocessor)