Debug 和 Release 並無本質的區別,他們只是VC預約義提供的兩組編譯選項的集合,編譯器只是按照預約的選項行動。若是咱們願意,咱們徹底能夠把Debug和Release的行爲徹底顛倒過來。固然也能夠提供其餘的模式,例如本身定義一組編譯選項,而後命名爲MY_ABC等。習慣上,咱們仍然更願意使用VC已經定義好的名稱。 數據庫
Debug版本包括調試信息,因此要比Release版本大不少(可能大數百K至數M)。至因而否須要DLL支持,主要看你採用的編譯選項。若是是基於 ATL的,則Debug和Release版本對DLL的要求差很少。若是採用的編譯選項爲使用MFC動態庫,則須要MFC42D.DLL等庫支持,而 Release版本須要MFC42.DLL支持。Release不對源代碼進行調試,不考慮MFC的診斷宏,使用的是 MFC Release庫,編譯時對應用程序的速度進行優化,而Debug則正好相反,它容許對源代碼進行調試,能夠定義和使用MFC的 診斷宏,採用MFC Debug庫,對速度沒有優化。 express
既然Debug和 Release僅僅是編譯選項的不一樣,那麼爲何要區分Debug和Release版本呢? 數組
Debug和Release,在我看來主要是針對其面 向的目標不一樣的而進行區分的。Debug一般稱爲調試版本,經過一系列編譯選項的配合,編譯的結果一般包含調試信息,並且不作任何優化,覺得開發 人員提供強大的應用程序調試能力。而Release一般稱爲發佈版本,是爲用戶使用的,通常客戶不容許在發佈版本上進行調試。因此不保存調試信 息,同時,它每每進行了各類優化,以期達到代碼最小和速度最優。爲用戶的使用提供便利。 多線程
下面僅就默認的Debug和Release版本的選項進行 比較,詳細的編譯選項能夠看MSDN的說明。 函數
咱們將默認的Debug和Release的選項設置進行比較,過濾掉相同設置,主要的不一樣以下: 工具
編譯選項:/Od /D "_DEBUG" /Gm /RTC1 /MDd /Fo"Debug「「" /ZI 性能
連接選項:/OUT:"D:「MyProject「logging「Debug「OptionTest.dll" /INCREMENTAL 優化
Release設置: ui
編譯選項:/O2 /GL /D "NDEBUG" /FD /MD /Fo"Release「「" /Zi spa
鏈 接選項:/OUT:"D:「MyProject「logging「Release「OptionTest.dll" /INCREMENTAL:NO
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 合併重 復的字符串,並將字符串常量放到只讀內存,防止被修改
MDd與MD
首 先,Debug版本使用調試版本的運行時庫(/MDd選項),Relase版本則使用的是發佈版本的運行時庫(vcrt.dll)。其區別主要在於運行時 的性能影響。調試版本的運行時庫包含了調試信息,並採用了一些保護機制以幫助發現錯誤,也所以,其性能不如發佈版本。編譯器提供的Runtime Library很穩定,不會形成Release版本錯誤,卻是因爲Debug版本的Runtime Library增強了對錯誤的檢測,如堆內存分配檢查等,反而會報告錯誤,應當指出,若是Debug有錯誤,而Release版本正常,程序確定是有 Bug的,只是咱們尚未發現。
ZI與Zi
其次,/ZI選項與/Zi選項。經過使用/ZI選項,能夠在調試過程修改代碼 而不須要從新編譯。這是個調試的好幫手,可若是咱們使用Release版本,這將變得不可行。
Od與O2
/O2與/Od 選項:Od是關閉編譯器優化,廣泛用於Debug版本。而O2選項是建立最快速代碼,這固然是Release版本的不二選擇。
RTCx選 項
/RTCx選項讓編譯器插入動態檢測代碼以幫助你檢測程序中的錯誤。好比,它會將局部變量初始化爲非零值。包括用 0xCC初始化全部自動變量,0xCD初始化堆中分配的內存(即new的內存),使用0xDD填充被釋放的內存(即delete的內存),0xFD初始化 受保護的內存(debug版在動態分配內存的先後加入保護內存以防止越界訪問)。這樣作的好處是這些值都很大,通常不可能做爲指針,考試,大提示做爲數值 也不多用到,並且這些值很容易辯認,所以有利於在Debug版本中發現Release版纔會遇到的錯誤。另外,經過函數指針調用函數時,會經過檢 查棧指針驗證函數調用的匹配性(防止原型不匹配)。使用/RTCx選項會形成Debug版本出錯,而Release版本正常的現象,由於 Release版中未初始化的變量是隨機的,極可能使指針指向了有效可是錯誤的地址,從而掩蓋了錯誤。這個編譯選項只能在/Od選項下使用。
Gm,INCREMENTAL or NO
編譯選項中的Gm和連接選項中的 INCREMENTAL都只爲一個目的,加快編譯速度。咱們常常趕上這樣的問題,只修改了一個頭文件,結果卻形成全部動態庫的從新編譯。而這兩個選項就是 爲了解決這樣的問題。若是啓用了/Gm開關,編譯器在項目中的.idb文件中存儲了源文件和類定義之間的依賴關係。以後的編譯過程當中使用.idb 文件中的信息肯定是否須要編譯某個源文件,哪怕是此源文件已經包含了已修改的.h文件。
INCREMENTAL開關默認是開啓的。使用增量連接生 成的可執行文件或者動態連接庫會大於非增量連接的程序,由於有代碼和數據的填充。另外,增量連接的文件還包含跳轉trunk以處理函數重定位到新地址。
MSDN 上明確指出:爲確保最終發佈版本不包含填充或者trunk,請非增量連接程序。
/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 版中未初始化的變量是隨機的,這有可能使指針指向一 個有效地址而掩蓋了非法訪問。
_DEBUG與NDEBUG
這是最重要的一個選項。這兩個是編譯器的預處理器定義,默認狀況下_DEBUG用於Debug版本,而NDEBUG用於Release版本。 它們能夠說是重要的無以復加。由於,assert系列的斷言僅僅在_DEBUG下生效!
下面是assert.h文件中摘出來的:
C++代碼
1. #ifdef NDEBUG
2. #define assert(_Expression) ((void)0)
3. #else /* NDEBUG */
4. #ifdef __cplusplus
5. extern "C" {
6. #endif /* __cplusplus */
7. _CRTIMP void __cdecl _wassert(__in_z const wchar_t * _Message, __in_z const wchar_t *_File, __in unsigned _Line);
8. #ifdef __cplusplus
9. }
10. #endif /* __cplusplus */
11. #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
12. #endif /* NDEBUG */
能夠看出在未定義_DEBUG時,assert變成一條空語句不被執行。
也就是說,咱們如今全部發布的版本沒法使 用斷言機制進行程序調試。
相關經驗:
1. 變量。
你們都知道,debug跟release在初始化變量時所作的操做是不一樣的,debug是將每一個字節位都賦成0xcc, 而release的賦值近似於隨機。若是你的程序中的某個變量沒被初始化就被引用,就頗有可能出現異常:用做控制變量將致使流程導向不一致;用做數組下標將會使程序崩潰;更加多是形成其餘變量的不許確而引發其餘的錯誤。因此在聲明變量後立刻對其 初始化一個默認的值是最簡單有效的辦法,不然項目大了你找都沒地方找。代碼存在錯誤在debug方式下可能會忽略而不被察覺到。debug方式下數組越界也大多不會出錯,在release中就暴露出來了,這個找起來就比較難了。
2. 自定義消息的消息參數。
MFC爲咱們提供了很好的消息機制,更增長了自定義消息,好處我就不用多說了。這也存在debug跟release的問 題嗎?答案是確定的。在自定義消息的函數體聲明時,時常會看到這樣的寫法:afx_msg LRESULT OnMessageOwn(); Debug 狀況下通常不會有任何問題,而當你在Release下且多線程或進程間使用了消息傳遞時就會致使無效句柄之類的錯誤。致使這個錯誤直接緣由是消息體的參數 沒有添加,即應該寫 成:afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); 3. release模式下不出錯,但debug模式下報錯。
這種狀況下大多也是由於代碼書寫不正確引發的,查看MFC 的源碼,能夠發現好多ASSERT的語句(斷言),這個宏只是在debug模式下才有效,那麼就清楚了,release版不報錯是忽略了錯誤而不是沒有錯誤,這可能存在很大的隱患,由於是Debug模式下,比較方便調試,好好的檢查本身的代碼,再此就很少說了。
3. ASSERT, VERIFY, TRACE.......... 調試宏
這種狀況很容易解釋。舉個例子:請在VC下輸入ASSERT而後選中按F12跳到宏定義的地方,這裏你就可以發現Debug中 ASSERT要執行AfxAssertFailedLine,而Release下的宏定義卻爲\"#define ASSERT(f) ((void)0)\"。因此注意在這些調試宏的語句不要用程序相關變量如i++寫操做的語句。
VERIFY 是個例外,\"#define VERIFY(f) ((void)(f))\",即執行。
哪些狀況下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"以後),函數原形錯誤時編譯會報錯
C++代碼
1. #undef ON_MESSAGE
2. #define ON_MESSAGE(message, memberFxn) \
3. { message, 0, 0, 0, AfxSig_lwl, \
4. (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL \
5. CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
(2) volatile 型變量:volatile 告訴編譯器該變量可能被程序以外的未知方式修改(如系統、其餘進程和線程)。優化程序爲了使程序性能提升,常把一些變量放在寄存器中(相似於 register 關鍵字),而其餘進程只能對該變量所在的內存進行修改,而寄存器中的 值沒變。若是你的程序是多線程的,或者你發現某個變量的值與預期的不符而你確信已正確的設置了,則極可能遇到這樣的問題。這種錯誤有時會表現爲程序在最快 優化出錯而最小優化正常。把你認爲可疑的變量加上 volatile 試試。
(3) 變量優化:優化程序會根據變量的使用狀況優化變量。例如,函數中有一個未被使用的變量,在 Debug 版中它有可能掩蓋一個數組越界,而在 Release 版中,這個變量極可能被 優化調,此時數組越界會破壞棧中有用的數據。固然,實際的狀況會比這複雜得多。與此有關的錯誤有:
非法訪問,包括數組越界、指針錯誤 等。例如
C++代碼
1. void fn(void)
2. {
3. int i;
4. i = 1;
5. int a[4];
6. {
7. int j;
8. j = 1;
9. }
10. a[-1] = 1; //固然錯誤不會這麼明顯,例以下標是變量
11. a[4] = 1;
12. }
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() 違反了斷言的思想,不能使程序代碼和調試代碼徹底分離, 最終可能會帶來不少麻煩。所以,專家們建議儘可能少用這個宏。
1、"Debug是調試版本,包括的程序信息更多"
補充:只有DEBUG版的程序才能設置斷點、單步執行、使用 TRACE/ASSERT等調試輸出語句。REALEASE不包含任何調試信息,因此體積小、運行速度快。
I. 內存分配問題
1. 變量未初始化。下面的程序在debug中運行的很好。
C++代碼
1. thing * search(thing * something)
2. BOOL found;
3. for(int i = 0; i < whatever.GetSize(); i++)
4. {
5. if(whatever[i]->field == something->field)
6. { /* found it */
7. found = TRUE;
8. break;
9. } /* found it */
10. }
11. if(found)
12. return whatever[i];
13. else
14. return NULL;
而在release中卻不行,由於debug中會自動給變量初始化found=FALSE,而在release版中則不會。因此儘量的給變量、類或結構 初始化。
2. 數據溢出的問題
如:
C++代碼
1. char buffer[10];
2. int counter;
3. 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宏是這樣定義的
C++代碼
1.
2. #ifdef _DEBUG
3. #define ASSERT(x) if( (x) == 0) report_assert_failure()
4. #else
5. #define ASSERT(x)
6. #endif
實際上覆雜一些,但可有可無。假如你在這些語句中加了程序中必需要有的代碼
好比
C++代碼
1.
2. ASSERT(pNewObj = new CMyClass);
3.
4. pNewObj->MyFunction();
這種時候Release版本中的pNewObj不會分配到空間
因此執行到下一個語句的時候程序會報該程序執行了非法操做的錯誤。這時能夠用VERIFY :
C++代碼
1.
2. #ifdef _DEBUG
3. #define VERIFY(x) if( (x) == 0) report_assert_failure()
4. #else
5. #define VERIFY(x) (x)
6. #endif
這樣的話,代碼在release版中就能夠執行了。
III. 參數問題:
自定義消息的處理函數,必須定義以下:
afx_msg LRESULT OnMyMessage(WPARAM, LPARAM);
返回值必須是HRESULT型,不然Debug會過,而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工程都進行改動。
另:
1. MS BUG:MS的一份技術文檔中代表,在VC5中對於DLL的"Maximize Speed"優化選項 並未被徹底支持,所以這將會引發內存錯誤並致使程序崩潰。
2. http://www.sysinternals.com/有 一個程序DebugView,用來捕捉OutputDebugString的輸出,運行起來後(估計是自設爲system debugger)就能夠觀看全部程序的OutputDebugString的輸出。此後,你能夠脫離VC來運行你的程序並觀看調試信息。
3. 有一個叫Gimpel Lint的靜態代碼檢查工具,聽說比較好用http://www.gimpel.com/ 不過要化$的。
Debug與Release不一樣的問題在剛開始編寫代碼時會常常發生,99%是由於你的代碼書寫錯誤而致使的,因此不要動不動就說系統問 題或編譯器問題,努力找找本身的緣由纔是根本。我從前就經常遇到這狀況,經歷過一次次的教訓後我就開始注意了,如今我所寫過的代碼我已經很久沒遇到這種問 題了。下面是幾個避免的方面,即便沒有這種問題也應注意一下:
1. 注意變量的初始化,尤爲是指針變量,數組變量的初始化(很大的狀況下另做考 慮了)。
2. 自定義消息及其餘聲明的標準寫法
3. 使用調試宏時使用後最好註釋掉
4. 儘可能使用 try - catch(...)
5. 儘可能使用模塊,不但表達清楚並且方便調試。