有時候我們執行程式時會遇到Debug版可以跑,但是Release版本反而不能跑了,為什麼呢?
首先, 先了解Debug和Release的主要差異:
Debug版:包含Microsoft格式的除錯資訊,沒有進行最佳化,檔案體積較大
Release版:不包含除錯資訊,有針對執行速度最佳化,檔案體積較小
事實上Debug 和 Release 並沒有本質的界限,他們只是一組編譯選項的集合,編譯器只是按照預定的選項行動.
Debug各參數的定義:
Release各參數的定義:
那些情況下Release版的程式會出錯? - Runtime Library差異(MDd和MD)
Runtime Library 包含了除錯用的訊息,並採用了一些保護機制以幫助發現錯誤,所以性能上較不如Release的版本。編譯器提供的 Runtime Library通常很穩定不會造成 Release 版本錯誤;倒是由於 Debug 的 Runtime Library 加強了對錯誤的檢測,如Heap的記憶體分配,有時會出現Debug 有錯但 Release 正常的現象。這裡要注意的是,如果 Debug 有錯,即使 Release 正常,程式保證是有 Bug 的,只不過可能是 Release 版的某次執行沒有表現出來而已。
在函式的呼叫過程中,所有呼叫的訊息(回傳位址、參數)以及自動變數都是放在堆疊中的。如果函式的宣告跟實作不同(參數、回傳值、呼叫方式),就會產生錯誤,但 Debug 方式下,堆疊的存取透過 EBP 暫存器儲存的位址來實現,如果沒有發生陣列越界之類的錯誤(或是越界「不多」),函式通常能正常執行;Release 方式下,最佳化會省略 EBP 堆疊基本位址的指標,這樣通過一個全域指標存取堆疊,可能就會造成回傳位址錯誤導致整個程式崩潰。
當您建置專案進行偵錯時,您是使用偵錯記憶體配置器 (Debug Memory Allocator)。這表示所有的記憶體配置周圍都放置了保護位元組。這些保護位元組會偵測記憶體覆寫。由於Release的組建與Debug版的堆積配置不同,在Debug中的記憶體覆寫可能不會造成任何問題,但在Release的組建中卻可能產生災難性的影響。
Deug通常會將記憶體和變數做初始化,包括用 0xCC 初始化所有自動變數,0xCD(Cleared Data)初始化Heap中分配的記憶體(即動態分配的記憶體,例如 new ),0xDD ( Dead Data ) 填充已被釋放的Heap記憶體(例如delete),0xFD(deFencde Data)初始化受保護的記憶體(debug 版在動態分配記憶體的前後加入保護記憶體以防止越界存取).
Release 版中未初始化的變量是隨機的(沒有初始化),這有可能使指針指向一個有效地址而掩蓋了非法訪問.
當定義了 _DEBUG 時,ASSERT() 函式會被編譯,而 NDEBUG 時不被編譯。此外,TRACE() 巨集的編譯也受 _DEBUG 控制有些人會自行定義#ifdef _DEBUG 之類的條件編譯,必須注意。
解決Release 版出錯的問題
前面已經提過,Debug和Release只是一組編譯選項的差別,實際上並沒有什麼定義能區分二者。我們可以修改Release版的編譯選項來縮小錯誤範圍。如上所述,可以把Release 的選項逐個改為與之相對的Debug選項,如/MD改為/MDd、/O1改為/Od,或運行時間最佳化改為程序大小最佳化。 注意,一次只改一個選項,看改哪個選項時錯誤消失,再對應該選項相關的錯誤,針對性地查找。
- 對變數作初始化
- 檢查ASSERT(),TRANCE()和#ifdef _DEBUG 之類的條件編譯的使用
- 注意VC的警告訊息
在 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
//暫時改變警告級別,有時你可以只在認為可疑的那一部分程式碼使用
DebugView : 可用來捕捉OutputDebugString(),可以在VC的環境外執行程式查看除錯訊息.
Gimpel Lint :靜態代碼檢查工具,不過要花$.
SmartChecker: Rum-Time錯誤檢查工具
BoundsChecker: Rum-Time錯誤檢查工具