當前位置:編程學習大全網 - 編程語言 - 調試版本出錯而發布版本運行正常是什麽原因?

調試版本出錯而發布版本運行正常是什麽原因?

首先,調試和發布編譯方法之間的本質區別

Debug通常稱為調試版本,包含調試信息,不做任何優化,方便程序員調試程序。Release被稱為release version,它往往會進行優化,使程序在代碼大小和運行速度上達到最優,讓用戶用得好。

調試和發布的真正秘密在於壹組編譯選項。下面列出了兩者的選項(當然也有其他的,比如/Fd /Fo,但區別並不重要,通常不會造成發布錯誤,這裏就不討論了)。

調試版本:

/MDd /MLd或/MTd使用調試運行時庫(運行時函數庫的調試版本)。

/Od關閉優化開關

/D "_DEBUG "等效於#define _DEBUG。打開編譯和調試代碼開關(主要用於

斷言功能)

/ZI創建壹個“編輯並繼續”數據庫,以便調試後。

如果在此過程中修改了源代碼,則無需重新編譯。

/GZ可以幫助捕捉內存錯誤。

/Gm打開最小化重新鏈接開關以減少鏈接時間。

發布版本:

/MD /ML或/MT使用發布版本的運行時庫。

/O1或/O2優化開關,使程序最小或最快。

/D "NDEBUG "關閉條件編譯調試代碼開關(即不編譯assert函數)。

/GF合並重復的字符串並將字符串常數放在只讀內存中以防止

被修改

實際上,Debug和Release之間並沒有本質的界限,它們只是壹組編譯選項,編譯器只是按照預定的選項來動作。事實上,我們甚至可以修改這些選項,以獲得優化的調試版本或帶有跟蹤語句的發布版本。

二、什麽情況下發布版本會出錯?

有了上面的介紹,我們再來逐壹對比這些選項,看看發布版本錯誤是如何產生的。

1.運行時庫:鏈接哪個運行時函數庫通常只會影響程序的性能。運行時庫的調試版本包含調試信息,並采用壹些保護機制來幫助發現錯誤,所以性能不如發布版本。編譯器提供的運行時庫通常是穩定的,不會導致發布版本錯誤;相反,Debug的運行時庫加強了對錯誤的檢測,比如堆內存分配,有時會出現Debug有錯誤但Release正常的現象。需要指出的是,如果Debug有問題,即使Release正常,也壹定是程序有問題,但可能是某次運行Release版本沒有顯示出來。

2.優化:這是產生錯誤的主要原因,因為關閉優化時基本上直接翻譯源程序,打開優化後編譯器會做壹系列假設。這種錯誤主要包括以下類型:

(1)框架指針省略號(簡稱FPO):在函數調用過程中,所有的調用信息(返回地址,參數)和自動變量都放入堆棧中。如果函數的聲明與實現(參數、返回值、調用方式)不同,就會出現錯誤——但在調試模式下,對堆棧的訪問是通過保存在EBP寄存器中的地址實現的,如果沒有數組交叉(或者“不多”交叉)等錯誤,通常可以正常執行函數;在釋放模式下,優化將省略EBP堆棧基址指針,因此通過全局指針訪問堆棧將導致返回地址錯誤和程序崩潰。C++的強類型特性可以檢測到大多數錯誤,但是如果使用強制類型轉換,就不能檢測到。您可以強制/Oy- compile選項關閉發布版本中的幀指針省略號,以確定是否會出現這種錯誤。此類錯誤通常包括:

● MFC消息響應函數寫入錯誤。正確的說法應該是

afx _ msg LRESULT OnMessageOwn(WPARAM WPARAM,LPARAM LPARAM);

ON_MESSAGE宏包含強制轉換。防止這種錯誤的方法之壹是重新定義ON_MESSAGE宏,並將以下代碼添加到stdafx.h中(在# include“afx win . h”之後)。當函數的原型出錯時,編譯會報錯。

#undef打開消息

#define ON_MESSAGE(message,memberFxn) { message,0,0,0,AfxSig_lwl,(AFX _ PMSG)(AFX _ PMS GW)(static _ cast & lt;LRESULT(AFX _ MSG _ CALL CWnd::*)(WPARAM,LPARAM)& gt;(& ampmemberFxn) },

(2) volatile變量:volatile告訴編譯器,該變量可能被程序外的未知手段(如系統、其他進程、線程)修改。為了提高程序的性能,優化器往往會將壹些變量放在寄存器中(類似於register關鍵字),而其他進程只能修改變量所在的內存,寄存器中的值保持不變。如果妳的程序是多線程的,或者妳發現壹個變量的值不是妳所期望的,並且妳確定它已經被正確設置,妳很可能會遇到這樣的問題。這種錯誤有時說明程序在最快的優化中是錯誤的,在最小的優化中是正常的。嘗試在妳認為可疑的變量中加入volatile。

(3)變量優化:優化器會根據變量的用途對變量進行優化。比如函數中有壹個未使用的變量,在調試版本中可能會掩蓋壹個數組越界,但在發布版本中,這個變量很可能會被優化,越界的數組會破壞堆棧中有用的數據。當然,實際情況會比這復雜得多。與此相關的錯誤有:

●非法訪問,包括數組越界、指針錯誤等。例如

無效fn(無效)

{

int I;

I = 1;

int a[4];

{

int j;

j = 1;

}

a[-1]= 1;//當然誤差不是那麽明顯,比如下標是變量。

a[4]= 1;

}

雖然當數組越界時J不在範圍內,但是它的空間沒有被恢復,所以I和J會掩蓋越界。但是發布版本可能會因為I和J作用不大而優化,會破壞棧。

3._DEBUG和NDEBUG:定義_DEBUG時,將編譯assert()函數,而不編譯NDEBUG。此外,VC++中還有壹系列斷言宏。這包括:

ANSI C asserts void assert(int表達式);

c運行時Lib asserts _ ASSERT(boolean expression);

_ ASSERTE(boolean expression);

MFC asserts ASSERT(boolean expression);

VERIFY(boolean expression);

ASSERT _ VALID(po object);

ASSERT_KINDOF( classname,po object);

ATL斷言ATLASSERT(boolean expression);

另外,TRACE()宏的編譯也是由_DEBUG控制的。

所有這些斷言只在調試版本中編譯,在發布版本中被忽略。唯壹的例外是VERIFY()。其實這些宏都調用了assert()函數,只是附上了壹些與庫相關的調試代碼。如果妳給這些宏添加任何程序代碼,而不僅僅是布爾表達式(比如賦值,可以改變變量值的函數調用等。),那麽發布版本將不會執行這些操作,從而導致錯誤。新手容易犯這樣的錯誤,搜索方法簡單,因為上面列出了這些宏。只需使用VC++的在文件中查找功能,找到所有工程文件中使用這些宏的地方,然後逐壹檢查。另外,有些專家可能還會添加#ifdef _DEBUG之類的條件編譯,要註意。

順便說壹下,值得壹提的是VERIFY()宏,它允許您將程序代碼放入布爾表達式中。這個宏通常用於檢查Windows API的返回值。有些人可能會因為這個原因濫用VERIFY()。其實這是很危險的,因為VERIFY()違背了斷言的思想,不能將程序代碼和調試代碼完全分開,最終可能會帶來很多麻煩。所以專家建議盡量少用這個宏。

4./GZ選項:該選項將執行以下操作。

(1)初始化內存和變量。包括用0xCC初始化所有自動變量,用0xCD (Cleared Data)初始化堆中分配的內存(即動態分配的內存,如new),用0xDD (Dead Data,如delete)填充釋放的堆內存,0x FD(defencedata)初始化保護內存(調試版在動態內存分配前後增加保護內存,防止越界訪問),其中括號內的字是微軟建議的助記符。這樣做的好處是,這些值非常大,所以不可能把它們作為指針使用(而且32位系統中的指針很少是奇值,在某些系統中奇值指針會引起運行時錯誤),它們作為值很少遇到,這些值很容易識別,所以只有在發布版本中才能發現調試版本中會遇到的錯誤,這是非常有益的。需要註意的是,很多人認為編譯器會用0初始化變量,這是錯誤的(而且也不利於發現錯誤)。

(2)通過函數指針調用函數時,會通過檢查堆棧指針來驗證函數調用的匹配性。(防止原型不匹配)

(3)函數返回前檢查堆棧指針,確認沒有被修改。(為防止越界訪問和原型不匹配,結合第二項,可以大致模擬框架指針省略FPO。)

通常情況下,/GZ選項會導致調試版出錯,發布版正常,因為發布版中未初始化的變量是隨機的,可能會使指針指向有效地址,掩蓋非法訪問。

此外,/Gm /GF等選項很少會導致錯誤,它們的效果很明顯,很容易找到。

第三,如何“調試”發布程序

遇到調試成功但發布失敗顯然是壹件非常沮喪的事情,而且往往無從下手。如果看了上面的分析,結合錯誤的具體表現,很快找出錯誤就好了。但是如果妳壹時找不到,這種情況下有壹些策略。

1.如前所述,Debug和Release只是壹組編譯選項的區別,實際上並沒有定義來區分。我們可以修改發布版本的編譯選項來縮小錯誤範圍。如上所述,您可以將Release的選項逐個更改為相應的調試選項,例如/MD到/MDd、/O1到/Od,或者運行時優化到程序大小優化。註意:壹次只更改壹個選項,查看更改哪個選項時錯誤消失,然後查找與該選項相關的錯誤。這些選項可以直接從項目\設置的列表中選擇...,並且通常不需要手動修改。因為上面的分析比較全面,所以這個方法是最有效的。

2.在編程過程中,要時刻註意測試發布版本,避免代碼過多,時間緊張。

3.在調試版本中使用/W4警告級別,這樣可以從編譯器中獲得最大限度的錯誤信息。例如,如果(i =0)將導致/W4警告。不要忽略這些警告,它們通常是由程序中的錯誤引起的。但是有時候/W4會帶來很多冗余信息,比如對未使用函數參數的警告,很多消息處理函數會忽略壹些參數。我們可以使用

#progma警告(禁用:4702) //禁止

// ...

#progma警告(默認值:4702) //再次允許

暫時禁用警告,或者使用

#progma warning(push,3) //將警告級別設置為/W3。

// ...

#progma警告(pop) //重置為/W4

要臨時更改警告級別,有時可以只在您認為可疑的代碼部分使用/W4。

4.妳也可以像Debug壹樣調試妳的發布版本,只是添加調試符號。在項目/設置中...,選擇“Win32版本”的設置,選擇C/C++選項卡,選擇“常規”作為類別,選擇“程序數據庫”作為調試信息。在鏈接標簽項目選項的末尾添加“/OPT:REF”(不要失去引號)。這允許調試器使用pdb文件中的調試符號。但是調試的時候妳會發現斷點很難設置,變量很難找到——這些都是優化過的。然而,幸運的是,調用堆棧窗口仍然正常工作。即使優化了幀指針,仍然可以找到堆棧信息(尤其是返回地址)。這對定位誤差很有幫助。

  • 上一篇:什麽是STEAM maker教育?
  • 下一篇:寧夏警官職業學院中專部專業有哪些?專業介紹
  • copyright 2024編程學習大全網