最近MFC写了个程序,生成release版,原来正常,后来删掉了些控件再编译运行,结果竟然报内存读写错误,debug却是正常的。后来将“Project   Settings”   中   “C++/C    ”   项目下优化选项改为Disbale(Debug),就又能正常运行了。

DEBUG和RELEASE   版本差异及调试相关问题【转】
                                                                                  
I.   内存分配问题     
1.   变量未初始化。下面的程序在debug中运行的很好。   
           thing   *   search(thing   *   something)   
           BOOL   found;   
           for(int   i   =   0;   i   <   whatever.GetSize();   i++)   
           {   
               if(whatever[i]->field   ==   something->field)   
               {   /*   found   it   */   
                     found   =   TRUE;   
                     break;   
                }   /*   found   it   */   
            }   
            if(found)   
                  return   whatever[i];   
           else   
                  return   NULL;   
而在release中却不行,因为debug中会自动给变量初始化found=FALSE,而在release版中则不会。所以尽可能的给变量、类或结构初始化。   
2. 数据溢出的问题       
         如:char   buffer[10];   
                int   counter;     
                lstrcpy(buffer,   "abcdefghik");   
在debug版中buffer的NULL覆盖了counter的高位,但是除非counter>16M,什么问题也没有。但是在release版中,counter可能被放在寄存器中,这样NULL就覆盖了buffer下面的空间,可能就是函数的返回地址,这将导致ACCESS   ERROR。   
3.DEBUG版和RELEASE版的内存分配方式是不同的   。如果你在DEBUG版中申请       ele   为   6*sizeof(DWORD)=24bytes,实际上分配给你的是32bytes(debug版以32bytes为单位分配),   而在release版,分配给你的就是24bytes(release版以8bytes为单位),所以在debug版中如果你写ele[6],可能不会有什么问题,而在release版中,就有ACCESS   VIOLATE。     
    
II.   ASSERT和VERIFY     
1. ASSERT在Release版本中是不会被编译的。   
ASSERT宏是这样定义的   
                  #ifdef   _DEBUG   
                  #define   ASSERT(x)   if(   (x)   ==   0)   report_assert_failure()   
                  #else   
                  #define   ASSERT(x)   
                  #endif   
                  实际上复杂一些,但无关紧要。假如你在这些语句中加了程序中必须要有的代码,比如     
ASSERT(pNewObj   =   new   CMyClass);   
pNewObj->MyFunction();   
这种时候Release版本中的pNewObj不会分配到空间   
所以执行到下一个语句的时候程序会报该程序执行了非法操作的错误。这时可以用VERIFY   :   
                  #ifdef   _DEBUG   
                  #define   VERIFY(x)   if(   (x)   ==   0)   report_assert_failure()   
                  #else   
                  #define   VERIFY(x)   (x)   
                  #endif   
这样的话,代码在release版中就可以执行了。   
    
III.参数问题:     
自定义消息的处理函数,必须定义如下:   
afx_msg   LRESULT   OnMyMessage(WPARAM,   LPARAM);   
返回值必须是HRESULT型,否则Debug会过,而Release出错、同时,WPARAM,LPARAM这两个参数一定要申明,否则Release版本能运行,但肯定错误。切记。   
    
IV.     内存分配     
保证数据创建和清除的统一性:如果一个DLL提供一个能够创建数据的函数,那么这个DLL同时应该提供一个函数销毁这些数据。数据的创建和清除应该在同一个层次上。   
    
V.   DLL的灾难     
人们将不同版本DLL混合造成的不一致性形象的称为   "动态连接库的地狱"(DLL   Hell)   ,甚至微软自己也这么说(http://msdn.microsoft.com/library/techart/dlldanger1.htm)。     
                如果你的程序使用你自己的DLL时请注意:     
1. 不能将debug和release版的DLL混合在一起使用。debug都是debug版,release版都是release版。解决办法是将debug和release的程序分别放在主程序的debug和release目录下     
2. 千万不要以为静态连接库会解决问题,那只会使情况更糟糕。   
      
VI. RELEASE板中的调试   :     
1. 将ASSERT()   改为   VERIFY()   。找出定义在"#ifdef   _DEBUG"中的代码,如果在RELEASE版本中需要这些代码请将他们移到定义外。查找TRACE(...)中代码,因为这些代码在RELEASE中也不被编译。   请认真检查那些在RELEASE中需要的代码是否并没有被便宜。   
2. 变量的初始化所带来的不同,在不同的系统,或是在DEBUG/RELEASE版本间都存在这样的差异,所以请对变量进行初始化。   
3. 是否在编译时已经有了警告?请将警告级别设置为3或4,然后保证在编译时没有警告出现.

VII. 将Project   Settings"   中   "C++/C   "   项目下优化选项改为Disbale(Debug)。编译器的优化可能导致许多意想不到的错误,请参考http://www.pgh.net/~newcomer/debug_release.htm   
1. 此外对RELEASE版本的软件也可以进行调试,请做如下改动:     
在"Project   Settings"   中   "C++/C   "   项目下设置   "category"   为   "General"   并且将"Debug   Info"设置为   "Program   Database"。   
在"Link"项目下选中"Generate   Debug   Info"检查框。     
"Rebuild   All"     
如此做法会产生的一些限制:     
无法获得在MFC   DLL中的变量的值。     
必须对该软件所使用的所有DLL工程都进行改动。     
另:   
MS   BUG:MS的一份技术文档中表明,在VC5中对于DLL的"Maximize   Speed"优化选项并未被完全支持,因此这将会引起内存错误并导致程序崩溃。   
2. www.sysinternals.com有一个程序DebugView,用来捕捉OutputDebugString的输出,运行起来后(估计是自设为system   debugger)就可以观看所有程序的OutputDebugString的输出。此后,你可以脱离VC来运行你的程序并观看调试信息。     
3. 有一个叫Gimpel   Lint的静态代码检查工具,据说比较好用。http://www.gimpel.com   不过要化$的。

《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《

--------------------------------------
本文主要包含如下内容:
1. Debug 和 Release 编译方式的本质区别
2. 哪些情况下 Release 版会出错
3. 怎样“调试” Release 版的程序
--------------------------------------
            关于Debug和Release之本质区别的讨论

一、Debug 和 Release 编译方式的本质区别

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
    Debug 和 Release 的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release 版错误,在此不讨论)
    
Debug 版本:
/MDd /MLd 或 /MTd   使用 Debug runtime library(调试版本的运行时刻函数库)
/Od                 关闭优化开关
/D "_DEBUG"         相当于 #define _DEBUG,打开编译调试代码开关(主要针对
                     assert函数)
/ZI                 创建 Edit and continue(编辑继续)数据库,这样在调试过
                     程中如果修改了源代码不需重新编译
/GZ                 可以帮助捕获内存错误
/Gm                 打开最小化重链接开关,减少链接时间
                     
Release 版本:       
/MD /ML 或 /MT      使用发布版本的运行时刻函数库
/O1 或 /O2          优化开关,使程序最小或最快
/D "NDEBUG"         关闭条件编译调试代码开关(即不编译assert函数)
/GF                 合并重复的字符串,并将字符串常量放到只读内存,防止
                     被修改

实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。
    
二、哪些情况下 Release 版会出错

有了上面的介绍,我们再来逐个对照这些选项看看 Release 版错误是怎样产生的
    
1. Runtime Library:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的 Runtime Library 通常很稳定,不会造成 Release 版错误;倒是由于 Debug 的 Runtime Library 加强了对错误的检测,如堆内存分配,有时会出现 Debug 有错但 Release 正常的现象。应当指出的是,如果 Debug 有错,即使 Release 正常,程序肯定是有 Bug 的,只不过可能是 Release 版的某次运行没有表现出来而已。

2. 优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种:

(1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误————但 Debug 方式下,栈的访问通过 EBP 寄存器保存的地址实现,如果没有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release 方式下,优化会省略 EBP 栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以确定是否此类错误。此类错误通常有:
     
     ● MFC 消息响应函数书写错误。正确的应为
      afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam);
      ON_MESSAGE 宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE 宏,把下列代码加到 stdafx.h 中(在#include "afxwin.h"之后),函数原形错误时编译会报错
      #undef ON_MESSAGE
      #define ON_MESSAGE(message, memberFxn) \
      { message, 0, 0, 0, AfxSig_lwl, \
      (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL \
      CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
      
    (2) volatile 型变量:volatile 告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。
    
    (3) 变量优化:优化程序会根据变量的使用情况优化变量。例如,函数中有一个未被使用的变量,在 Debug 版中它有可能掩盖一个数组越界,而在 Release 版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有:
     ● 非法访问,包括数组越界、指针错误等。例如
         void fn(void)
         {
           int i;
           i = 1;
           int a[4];
           {
             int j;
             j = 1;
           }
           a[-1] = 1;//当然错误不会这么明显,例如下标是变量
           a[4] = 1;
         }
       j 虽然在数组越界时已出了作用域,但其空间并未收回,因而 i 和 j 就会掩盖越界。而 Release 版由于 i、j 并未其很大作用可能会被优化掉,从而使栈被破坏。

3. _DEBUG 与 NDEBUG :当定义了 _DEBUG 时,assert() 函数会被编译,而 NDEBUG 时不被编译。除此之外,VC++中还有一系列断言宏。这包括:

ANSI C 断言         void assert(int expression );
    C Runtime Lib 断言 _ASSERT( booleanExpression );
                        _ASSERTE( booleanExpression );
    MFC 断言            ASSERT( booleanExpression );
                        VERIFY( booleanExpression );
                        ASSERT_VALID( pObject );
                        ASSERT_KINDOF( classname, pobject );
    ATL 断言            ATLASSERT( booleanExpression );
    此外,TRACE() 宏的编译也受 _DEBUG 控制。

所有这些断言都只在 Debug版中才被编译,而在 Release 版中被忽略。唯一的例外是 VERIFY() 。事实上,这些宏都是调用了 assert() 函数,只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码,而不只是布尔表达式(例如赋值、能改变变量值的函数调用 等),那么 Release 版都不会执行这些操作,从而造成错误。初学者很容易犯这类错误,查找的方法也很简单,因为这些宏都已在上面列出,只要利用 VC++ 的 Find in Files 功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入 #ifdef _DEBUG 之类的条件编译,也要注意一下。
    顺便值得一提的是 VERIFY() 宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查 Windows API 的返回值。有些人可能为这个原因而滥用 VERIFY() ,事实上这是危险的,因为 VERIFY() 违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。

4. /GZ 选项:这个选项会做以下这些事

(1) 初始化内存和变量。包括用 0xCC 初始化所有自动变量,0xCD ( Cleared Data ) 初始化堆中分配的内存(即动态分配的内存,例如 new ),0xDD ( Dead Data ) 填充已被释放的堆内存(例如 delete ),0xFD( deFencde Data ) 初始化受保护的内存(debug 版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且 32 位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在 Debug 版中发现 Release 版才会遇到的错误。要特别注意的是,很多人认为编译器会用 0 来初始化变量,这是错误的(而且这样很不利于查找错误)。
    (2) 通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配)
    (3) 函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略 FPO )
    
    通常 /GZ 选项会造成 Debug 版出错而 Release 版正常的现象,因为 Release 版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。
    
除此之外,/Gm /GF 等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。

三、怎样“调试” Release 版的程序

遇到 Debug 成功但 Release 失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。
    
    1. 前面已经提过,Debug 和 Release 只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改 Release 版的编译选项来缩小错误范围。如上所述,可以把 Release 的选项逐个改为与之相对的 Debug 选项,如 /MD 改为 /MDd、/O1 改为 /Od,或运行时间优化改为程序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在 Project\Settings... 中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。

2. 在编程过程中就要时常注意测试 Release 版本,以免最后代码太多,时间又很紧。
    
    3. 在 Debug 版中使用 /W4 警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug 引起的。但有时 /W4 会带来很多冗余信息,如 未使用的函数参数 警告,而很多消息处理函数都会忽略某些参数。我们可以用
      #progma warning(disable: 4702) //禁止
      //...
      #progma warning(default: 4702) //重新允许
来暂时禁止某个警告,或使用
      #progma warning(push, 3) //设置警告级别为 /W3
      //...
      #progma warning(pop) //重设为 /W4
来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4。

4.你也可以像 Debug 一样调试你的 Release 版,只要加入调试符号。在 Project/Settings... 中,选中 Settings for "Win32 Release",选中 C/C++ 标签,Category 选 General,Debug Info 选 Program Database。再在 Link 标签 Project options 最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到——这些都被优化过了。不过令人庆幸的是,Call Stack 窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。

MFC中release版本和debug版本区别的更多相关文章

  1. Release 版本和 Debug 版本

    什么是 Release 版本.Debug 版本? bug-缺陷,程序故障.而debug指的是排除缺陷,显然这个模式是面向开发者的. 而release是满足发布所用. Debug 和 Release,在 ...

  2. Flutter 发布APK时,release版本和debug版本的默认权限不同

    Flutter 发布APK时,release版本和debug版本的默认权限不同 @author ixenos 在调试模式下,默认情况下启用服务扩展和多个权限(在flutter中) 当您处于发布模式时, ...

  3. Web.config的Release版本和Debug版本不一样的奥秘

      VS编译完后,release版本的web.config(或者app.config) 为什么不一样那? 我们查看一下项目结构,会发现有两个版本的config文件存在: 打开web.Debug.con ...

  4. 最全Windows版本jemalloc库(5.2.1)及其使用:包含动态库和静态库、x86版本和x64版本、debug版本和release版本

    编写服务器程序时,需要频繁的申请和释放内存,长时间运行会产生大量的内存碎片,这就导致即使当前系统中的闲置内存还足够多,但也无法申请到大的连续可用的内存块,因为此时的物理内存已经千疮百孔像个马蜂窝.此外 ...

  5. VS编译静态库 .lib 其中Release 版本比Debug版本要大好多原因

    如果工程代码使用了: 把此选项关闭即可减少库大小不少:

  6. 【FlashPlayer】-Debug版本-开发人员推荐

    Adobe的Flash-Player的版本分为“普通版本”和“Debug版本”,后者可以协助开发者测试和调试程序. 那么,先给出Debug版本的下载地址:http://www.adobe.com/su ...

  7. 【转】MFC中调试过程中查看输出信息 -- 不错

    原文网址:http://blog.sina.com.cn/s/blog_4e24d9c501014o39.html 笔记&&方便查阅. ~~~~~~~~~~~~~~~~~~~~~~~~ ...

  8. MFC中TRACE

    错误 1 error C1189: #error :  Building MFC application with /MD[d] (CRT dll version) requires MFC shar ...

  9. VC中release和debug版本的区别

    DEBUG版本 包含调试信息,编译器生成的代码方便调试 RELEASE版本 算是发布版本,RELEASE下不能调试,生成的文件更小,编译器生成的程序速度更快但是Debug下生成的exe文件在单独运行时 ...

随机推荐

  1. 区分listview的item和Button的点击事件

    这两天修改领导通的ListView widget,在ListView中加入Button这类的有 “点击” 事件的widget,发现原来listview的itemclick居然失效了, 后来在网上查资料 ...

  2. 子iframe 怎么调用 父级的JS函数

    window.parent.父级函数名();

  3. AOP 下的权限控制实现

    摘要  面向方面的编程(AOP)是一种新的编程技术,它弥补了面向对象的编程(OOP)在跨越模块行为上的不足.AOP 引进了 Aspect,它将影响多个类的行为封装到一个可重用模块中,它允许程序员对横切 ...

  4. RightBarButon

    //rightBar button UIButton *rightButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 34, 34)]; ...

  5. R中将list类型数据转换成data.frame型

    例如将如下数据转换成data.frame型: l <- replicate( 5, list(sample(letters, 4)), simplify = FALSE ) => 用unl ...

  6. C++学习32 重载new和delete运算符

    内存管理运算符 new.new[].delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数.一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内 ...

  7. 在备份和导入mysql数据库遇到的几个问题

    一.怎么导出和备份 1.普通方法,运用工具或者命令直接导出sql脚本,以navicat为例,直接选中数据库,转储sql文件 问题:当有视图或者函数执行失败时不好处理 2.视图函数和表数据分开导出 以n ...

  8. Intellisense for Xrm.Page in CRM 2011

    Intellisense for Xrm.Page in CRM 2011 In CRM 2011 javascripts for crm forms can be stored externally ...

  9. 关于 Python Iterator 协议的一点思考

    转:http://www.jianshu.com/p/dcf83643deeb Python 中有好几种容器或者序列类型:list tuple dict set str,对于这些类型中的内容,往往需要 ...

  10. C Primer Plus(第五版)6

    第 6 章 C 控制语句 : 循环 在本章中你将学习下列内容 已经多次学过,没怎么标注 · 关键字: for while do while · 运算符: < > >= <= ! ...