VC 调试技巧

1.如何在Release状态下进行调试

  Project->Setting=>ProjectSetting对话框,选择Release状态。C/C++标签中的Category选General,Optimizations选Disable(Debug),Debut info选Program Database。在Link标签中选Generatedebu复选框。
注:只是一个介乎Debug和Release的中间状态,所有的ASSERT、VERIFY都不起作用,函数调用方式已经是真正的调用,而不查表,但是这种状态下QuickWatch、调用队列跟踪功能仍然有效,和Debug版一样。
(因为Release和Debug的成员变量初始值并不相同。如BOOL类型两个版本的初始值分别为FALSE和TRUE这个时候,两个版本的运行结果就会不一样,这也是很多人会碰到Debug版本能够正确运行,而Release版本不能正确运行的原因)
×××××××××××××××××××××××××××××××××××××××××××
调试Release版本应用程序
如果在您的开发过程中遇到了常见的错误,或许您的Release版本不能正常运行而Debug版本运行无误,那么我推荐您阅读本文:因为并非如您想象的那样,Release版本可以保证您的应用程序可以象Debug版本一样运行。

如果您在开发阶段完成之后或者在开发进行一段时间之内从来没有进行过Release版本测试,然而当您测试的时候却发现问题,那么请看我们的调试规则1:

规则1: 经常性对开发软件进行Debug和Release版本的常规测试.

测试Release版本的时间间隔越长,排除问题的难度越大,至少对Release版本进行每周1次的测试,可以使您在紧凑的开发周期内节省潜在的排故时间.

不要随意删除Release版本需要的代码
这点看起来似乎再明显不过,但却是开发人员无意中经常犯的错误,原因在于编译器编译Release版本时候会主动排除在代码中存在的宏,例如ASSERT和TRACE在Release版本会自动排除,这样导致的问题是您在这些宏当中运行的代码也被随之删除,这是非常危险的事情J,例如:

ASSERT(m_ImageList.Create(MAKEINTRESOURCE(IDB_IMAGES), 16, 1, RGB(255,255,255)));

这样的代码在Debug模式不会出错,图像列表也自动创建了,然而在Release版本呢?后继使用m_ImageList对象只会造成程序的Crash!,因此ASSERT宏中尽量使用逻辑运算符作为验证。

规则 2: 不要将代码放置在仅在某种编译选项中执行的地方,对于使用_DEBUG等编译选项宏内部的代码必须不影响整个程序的使用.

规则 3: 不要使用规则2作为评判标准来删除ASSERT宏,ASSERT宏是个有用的工具,但容易使用错误.


使Debug编译模式接近Release模式
如果您的Release版本存在的问题是由代码被编译器自动排除造成的,那么通过这个方法您的问题可能会重现.

一些问题的产生可能是由于不同编译选项之间预定义符号造成的,因此您可以更改编译模式下的预定义符号,从而使您的Debug模式接近Release模式,观察错误是否产生,更改编译预定义符号方法如下:

Alt-F7打开项目设置,在C++/C 页面,选择"General"类别,更改"_DEBUG"符号为"NDEBUG". 
在C++/C 页面, 选择"Preprocessor"类别,添加预定义符号"_DEBUG"到"Undefined Symbols"栏. 
使用"Rebuild All"重新编译
如果通过上面设置,您在Release编译模式下面的问题在Debug模式下重现,那么请您依据以下步骤对您的代码进行修改: 

查找ASSERT排除其中的所有重要执行语句,或者将ASSERT修改为VERIFY. 
检查"#ifdef _DEBUG" 内所有代码,排除Release模式使用的代码. 
查找TRACE 排除其中的所有重要执行语句. TRACE和ASSERT一样,仅在Debug模式下编译.
如果通过上面修改更正了您在Debug模式下的问题,那么您可以重新编译Release模式,非常有可能您可以解决先前存在的问题!.

错误的假定造成编译模式错误
您是否经常性的假定您的变量或者对象被初试化成某个指定的值(可能0)?您是否假定你所有关联到的资源在应用程序中都存在?这些也是Debug和Release模式下不同问题产生的原因. 

规则 4: 除非您在代码中对变量进行初始化,否则不能作出如上假定. 包括全局变量,自动变量,申请对象和new对象.

这种情况还常常发生在内存顺序的问题,记得原来使用结构体的时候为了使用方便,比较两个结构体对象使用memcmp,在Debug版本工作正常,而Release版本计算出错误的解,看来的确不能进行错误的假定!

规则 5: 确保删除资源的所有引用都被删除,例如resource.h中的定义.

软件开发中,不同编译版本对变量和内存的初始化是不同的. 如果您假定变量初始化为0,那么在Win9x系统的Release模式下,会出现异常现象。因此对所有变量,内存显式清0是较为安全的做法. 

如果您引用了已经被删除的资源,您的Debug版本可以正常工作,但是Release版本可能会crash.

您是否相信编译器? 
编译器警告级别和编译噪音有着相当大的关系.

通过提高编译器警告级别可增加程序隐藏问题暴露的机会.通常设置警告级别在"Level 3"或者 "Level 4".编译并解决所有警告,这是发布Release版本应用程序的一个很好的建议.这能暴露会使您的应用程序出现问题的很多初始化问题和其它潜在的错误. 

规则 6: 开始项目之前先将编译警告级别设置在"Level 3" 或者 "Level 4" ,登记代码之前确保消灭所有警告!.

总结报告
编译模式下的调试
曾经不止一次的听到一些VC开发者说Release模式下面不能进行调试,幸运的是:通过相应设置,可以在Release模式进行调试,因此那只不过是一个以讹传讹的荒谬说法而已. 

规则 7: 当前面所有的方法都无效的时候,在Release模式下面进行调试.

Release模式可以进行调试,第一步是打开符号表: 

Alt-F7打开项目设置,在C++/C 页面,选择"General"类,修改Debug Info setting 为 "Program Database". 
在"Link" 页面,选择"Generate Debug Info". 
"Rebuild All"
这些设置将允许您在Release模式下保留符号表,您也可以同时考虑以下设置: 

调试Release版本应用程序,您可以关闭优化选项. 
如果在Release模式下面不能设置断点,添加指令"__asm {int 3}" 可以是您的应用程序在改行停止(确定在发布应用程序时候排除这些代码).
在Release模式进行调试的几个限制. 

最大的问题在于您不能跟踪到MFC函数内部,原因在于Release版本的MFC动态链接库不包含调试信息和符号表. 
同上,想要调试调用的dll,您必须给它们全部加上调试信息和符号表.
编译器生成了错误的代码?
或许有的时候您会发现VC++编译器生成了’问题代码’,然而坦率的讲,人们通常抱怨的太早.您可以在Release模式下面关闭优化选项来进行测试. 

如果这个操作解决了您的问题,或许您的编码习惯存在问题. 信不信由你, 极其可能在您的编码中存在模棱两可的求解或者看起来似乎正确,某些条件下也是正确的情况. 举个例子,下面的代码在Debug模式似乎一切’正常’,而在Release模式下面却会出错! 

#include <stdio.h>

int* func1()
{
int retval = 5;
return &retval;
}

int main(int argc, char* argv[])
{
printf("%d\n", *func1());
return 0;
}
我相信大多数程序员尤其是初学者容易遇到此类情况的.

规则 8: 如果关闭Release模式的优化选项可以使您的应用程序运行正常,而打开优化选项则出现问题的化,原因多半在于您的不良编码习惯造成的. 这意味着必须仔细检查您的代码,清理出那些错误的假设,悬空指针等等. 等同的这告诉您,在Debug模式和关闭优化选项的Release模式下您的应用程序工作正常全是因为系统隐含的运气,您必须着手更正存在隐患的代码,否则在日后可能会造成巨大的损失.

规则 9: 如果您已经彻底检查了您的代码,并且没有发现问题,那么您最好逐个打开优化选项将产生错误的原因限制在某个范围之内. 

BTW- 以上问题代码由C++编译器自动检出. 如果您已经遵循 规则 6 您或许在前面环节中已经解决了这些问题.

凭我的开发经验,编译器极少会产生错误的代码(当然要注意接口程序边界对齐的问题).通常在使用模板类时候VC6编译器或许会产生断言ASSERT错误,这种情况您只需更新补丁即可解决.

最后的思考
在日常编码中只需稍微增加一点严格的检测,便能有效的避免新的Debug -v- Release模式问题的产生,以下是我的一些经验.

1. 取出(check out)需要修改的代码.

2. 修改代码,排除所有警告,编译Debug和Release版本. 

3. 详细测试新代码,即单步调试新代码段之后进入工作代码,确保代码无误. 

4. 更正所有问题. 

5. 确认无误之后将新代码登记入库(check in). 

6. 对登记入库的代码进行全新的编译,确保新登记代码与其它代码融合. 

7. 重新详细测试代码. 

8. 更正新问题(或许可以发现登记入库代码存在的问题)

严格按照以上步骤,您在设计开发过程中即可解决大量问题,避免在最后发布应用程序时候产生新的难以定位的问题. 

后记
本文是在我的开发历程中遇到Release版本应用程序发布,产生错误的时候苦苦求索得到的一些经验,原文来自于codeproject,经过本人润色,改写成为适合国内开发者的文章,希望能对大家有用,谢谢!
×××××××××××××××××××××××××××××××××××××××××××


2. Release和Debug有什么不同
  Release版称为发行版,Debug版称为调试版。
  Debug中可以单步执行、跟踪等功能,但生成的可执行文件比较大,代码运行速度较慢。Release版运行速度较快,可执行文件较小,但在其编译条件下无法执行调试功能。
Release的exe文件链接的是标准的MFC DLL(Use MFC in a shared or static dll)。这些DLL在安装Windows的时候,已经配置,所以这些程序能够在没有安装Visual C++ 6.0的机器上运行。而Debug版本的exe链接了调试版本的MFC DLL文件,在没有安装Visual C++6.0的机器上不能运行,因为缺相应的DLL,除非选择use static dll when link。

3. ASSERT和VERIFY有什么区别
  ASSERT里面的内容在Release版本中不编译,VERIFY里面的内容仍然翻译,但不再判断真假。所以后者更安全一点。
  例如ASSERT(file.Open(strFileName))。
一旦到了Release版本中,这一行就忽略了,file根本就不Open()了,而且没有任何出错的信息。如果用VERIFY()就不会有这个问题。

4.Workspace和Project之间是什么样的关系
每个Workspace可以包括几个project,但只有一个处于Active状态,各个project之间可以有依赖关系,在project的Setting..中可以设定,比如那个Active状态的project可以依赖于其他的提供其函数调用的静态库。

5. 如何在非MFC程序中使用ClassWizard
在工程目录下新建一个空的.RC文件,然后加入到工程中就可以了。

6.如何设置断点
  按F9在当前光标处增加一个断点和取消一个断点。
另外,在编辑状态下,按Ctrl+B组合键,弹出断点设置对话框。然后单击【Condition…】按钮弹出设置断点条件的对话框进行设置。

7.在编辑状态下发现成员变量或函数不能显示提示是如何打开显示功能
  这似乎是目前这个Visual C++ 6.0版本的一个bug,可按如下步骤使其正常,如再出现,可如法炮制:
  (1)关闭Project
  (2)删除“工程名.ncb”文件
(3)重新打开工程
8.如何将一个通过ClassWizard生成的类彻底删除
首先在工作区的FileView中选中该类的.h和.cpp文件,按delete删除,然后在文件管理器中将这两个文件删除,再运行ClassWizard,这时出现是否移走该类的提示,选择remove就可以了。

9. 如何将在workspace中消失的类找出来
打开该类对应的头文件,然后将其类名随便改一下,这个时候工作区就会出现新的类,再将这个类改回原来的名字就可以了。

10. 如何清除所有的断点
菜单【Edit】->【Breakpoints…】,打开“Breakpoints”对话框,单击【Remove All】按钮即可。快捷键是“Ctrl + Shift + F8”。

11. 如何再ClassWizard中选择未列出的信息
打开“ClassWizard”对话框,然后切换到“Class Info”页面。改变“Message filter”,如选择“Window”,“Message”页面就会出现Window的信息。

12. 如何检测程序中的括号是否匹配
把光标移动到需要检测的括号前面,按快捷键“Ctrl + ]”。如果括号匹配正确,光标就跳到匹配的括号处,否则光标不移动,并且机箱喇叭还会发出一声警告。

13. 如何查看一个宏(或变量、函数)的定义
把光标移动到要查看的一个宏上,就比如说最常见的DECLARE_MAP_MESSAGE上按一下F12(或右键菜单中的相关菜单),如果没有建立浏览文件,就会出现提示对话框,按【确定】按钮,然后就会跳到该宏(或变量、函数)定义的地方。

14. 如何添加Lib文件到当前工程
单击菜单【Project】->【Settings…】弹出“Project Setting”对话框,切换到“Link”标签页,在“Object/library modules”处输入Lib文件名称,不同的Lib之间用空格格开。

15. 如何快速删除项目下的Debug文件夹中临时文件
在工作区的FileView视图中选中对应的项目,单击右键弹出菜单,选择【Clean(selection only)】菜单即可。

16. 如何快速生成一个现有工程除了工程名外完全相同的新工程
  在新建工程的“New”对话框中选择“Custom Appwizard”项,输入新工程的名字,单击【OK】按钮。出现“Custom AppWizard”项,输入新工程的名字,单击【OK】按钮。出现“Custom AppWizard-Step 1 of 2”对话框,选择“An existing Project”项,单击【Next】按钮。出现“Custom AppWizard-Step 2 of 2”对话框,选择现有工程的工程文件名,最后单击【Finish】按钮。编译后就生成一个与现有工程相同但可以重新取名的工程AppWizard。
现在就可以项用MFC AppWizard一样用这个定制的向导。如果不想用了,可以在Visual C++ 6.0安装目录下Common\MSDev98\Template目录中删除该Wizard对应的.awx和.pdb文件。

17. 如何解决Visual C++ 6.0不正确连接的问题
  情景:明明改动了一个文件,却要把整个项目全部重新编译链接一次。刚刚链接好,一运行,又提示重新编译链接一次。
这是因为出现了未来文件(修改时间和创建时间比系统时间晚)的缘故。可以这样处理:找到工程文件夹下的debug目录,将创建和修改时间都比系统时间的文件全部删除,然后再从新“Rebuild All”一次。

18. 引起LNK2001的常见错误都有哪些
  遇到的LNK2001错误主要为:unresolved external symbol “symbol”
  如果链接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误信息。一般来说,发生错误的原因有两个:一是所引用的函数、变量不存在,拼写不正确或者使用错误;其次可能使用了不同版本的链接库。以下是可能产生LNK2001错误的原因:
  <1>由于编码错误导致的LNK2001错误
  (1)不相匹配的程序代码或模块定义(.DEF)文件导致LNK2001。例如,如果在C++源文件了内声明了一变量“var1”,却试图在另一个文件内以变量“var1”访问改变量。
  (2)如果使用的内联函数是在.cpp文件内定义的,而不是在头文件内定义将导致LNK2001错误。
  (3)调用函数时如果所用的参数类型和头函数声明时的类型不符将会产生LNK2001错误。
  (4)试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001错误。
  (5)要注意函数和变量的可公用性,只有全局变量、函数是可公用的。静态函数和静态变量具有相同的使用范围限制。当试图从文件外部方位任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001错误。
  <2>由于编译和联机的设置而造成的LNK2001错误
  (1)如果编译时使用的是/NOD(/NODERAULTLIB)选项,程序所需要的运行库和MFC时将得到又编译器写入目标文件模块,但除非在文件中明确包含这些库名,否则这些库不会被链接进工程文件。这种情况下使用/NOD将导致LNK2001错误
  (2)如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC时将出现“unresolved external on _WinMain@16”的LNK2001错误信息。
  (3)使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内,源文件中对“func”的引用,在目标文件里即对“__imp__func”的引用。如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行链接,将在__imp__func上发生LNK2001错误。如果不使用/MD选项编译,在使用MSVCxx.LIB链接时也会发生LNK2001错误。
  (4)使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001错误。
  (5)当编译调试版的应用程序时,如果采用发行版模态库进行链接也会产生LNK2001错误;同样,使用调试版模态库链接发行版应用程序时也会产生相同的错误。
  (6)不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可能包含早先的版本没有的符号和说明。
  (7)在不同的模块中使用内联和非内联的编译选项能够导致LNK2001错误。如果创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头文件里却关闭了函数内联(没有inline关键字),只是将得到错误信息。为避免该问题的发生,应该在相应的头文件中用inline关键字标志为内联函数。
(8)不正确的/SUBSYSTEM或ENTRY设置也能导致LNK2001错误。
19. 如何调试一个没有源码的exe文件调用的dll
在Visual C++ 6.0中,进入“Project Setting”对话框然后选择Debug标签页。通常Visual Studio默认“executable for debug session”为可执行文件名,但可以将他改成任何你想要的程序。甚至可以指定不同的工作目录以及传递参数到你的程序。这个技术常用来调试Dlls、名字空间扩展、COM对象和其他从某些EXE以及从第三方的EXE中调用的plug-in程序。

20. Visual C++ 6.0工程中的项目文件都表示什么
  .opt:工程关于开发环境的参数文件。如工具条位置等信息。
  .aps(AppStudio File)资源辅助文件,二进制格式,一般不用去管它。
  .clw:ClassWizard信息文件,实际上是INI文件格式,有兴趣可以研究一下。有时候ClassWizard出了问题,手工修改CLW文件可以解决。如果此文件不存在的话,每次用ClassWizard的时候回提示是否重建。
  .dsp(DevelopStudio Project):项目文件,文本格式,不过不熟悉的不要手工修改。
  .dsw(DevelopStudio Workspace):是工作区文件,其他特点和.dsp差不多。
  .plg:是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大。在单击菜单【Tool】->【Option】弹出的对话框里面有个选项可以控制这个文件的生成。
  .hpj(Help Project):是生成帮助文件的工程,用microsoft Help Compiler可以处理。
  .mdp(Microsoft DevStudio Project):是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的.dsp格式。
  .bsc:是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件。如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,这样可以加快编译速度。
  .map是执行文件的映象信息记录文件,除非对系统底层,这个文件一般用不着。
  .pch(Pre-Compiled File):是与编译文件,可以加快编译速度,但是文件非常大。
  .pdb(Program Database):记录了程序有关的一些数据和调试信息,在调试的时候可能有用。
  .exp:只有在编译DLL的时候才会生成,记录了DLL文件的一些信息,一般也没有用。
  .ncb:无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。编译工程后会自动生成。

posted on 2013-05-14 17:55  梦想Sky  阅读(2414)  评论(0编辑  收藏  举报

导航