引言
如果在您的開發過程中遇到了常見的錯誤,或許您的Release版本不能正常運行而Debug版本運行無誤,那么我推薦您閱讀本文:因為並非如您想象的那樣,Release版本可以保證您的應用程序可以象Debug版本一樣運行。 如果您在開發階段完成之后或者在開發進行一段時間之內從來沒有進行過Release版本測試,然而當您測試的時候卻發現問題,那么請看我們的調試規則1:
規則1:
經常性對開發軟件進行Debug和Release版本的常規測試. 測試Release版本的時間間隔越長,排除問題的難度越大,至少對Release版本進行每周1次的測試,可以使您在緊湊的開發周期內節省潛在的排故時間. 不要隨意刪除Release版本需要的代碼這點看起來似乎再明顯不過,但卻是開發人員無意中經常犯的錯誤,原因在於編譯器編譯Release版本時候會主動排除在代碼中存在的宏,例如ASSERT和TRACE在Release版本會自動排除,這樣導致的問題是您在這些宏當中運行的代碼也被隨之刪除,這是非常危險的事情,
例如: 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模式,觀察錯誤是否產生,
更改編譯預定義符號方法如下:
a.. Alt-F7打開項目設置,在C++/C 頁面,選擇"General"類別,更改"_DEBUG"符號為"NDEBUG".
b.. 在C++/C 頁面, 選擇"Preprocessor"類別,添加預定義符號"_DEBUG"到"Undefined Symbols"欄. c..
使用"Rebuild All"重新編譯如果通過上面設置,您在Release編譯模式下面的問題在Debug模式下重現,那么請您依據以下步驟對您的代碼進行修改:
a.. 查找ASSERT排除其中的所有重要執行語句,或者將ASSERT修改為VERIFY.
b.. 檢查"#ifdef _DEBUG" 內所有代碼,排除Release模式使用的代碼.
c.. 查找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模式可以進行調試,
第一步是打開符號表:
a.. Alt-F7打開項目設置,在C++/C 頁面,選擇"General"類,修改Debug Info setting 為 "Program Database".
b.. 在"Link" 頁面,選擇"Generate Debug Info". c.. "Rebuild All"
這些設置將允許您在Release模式下保留符號表,您也可以同時考慮以下設置:
a.. 調試Release版本應用程序,您可以關閉優化選項.
b.. 如果在Release模式下面不能設置斷點,添加指令"__asm {int 3}" 可以使您的應用程序在該行停止(確定在發布應用程序時候排除這些代碼).
在Release模式進行調試的幾個限制.
a.. 最大的問題在於您不能跟蹤到MFC函數內部,原因在於Release版本的MFC動態鏈接庫不包含調試信息和符號表.
b.. 同上,想要調試調用的dll,您必須給它們全部加上調試信息和符號表. 編譯器生成了錯誤的代碼? 或許有的時候您會發現VC++編譯器生成了’問題代碼’,然而坦率的講,人們通常抱怨的太早.您可以在Release模式下面關閉優化選項來進行測試. 如果這個操作解決了您的問題,或許您的編碼習慣存在問題. 信不信由你, 極其可能在您的編碼中存在模棱兩可的求解或者看起來似乎正確,某些條件下也是正確的情況.
舉個例子,下面的代碼在Debug模式似乎一切’正常’,而在Release模式下面卻會出錯!
#include 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,經過本人潤色,改寫成為適合國內開發者的文章,希望能對大家有用,謝謝!
=====================================================================================================================================================================================
轉載: Debug版本包括調試信息,所以要比Release版本大很多(可能大數百K至數M)。至於是否需要DLL支持,主要看你采用的編譯選項。如果是基於ATL的,則Debug和Release版本對DLL的要求差不多。如果采用的編譯選項為使用MFC動態庫,則需要MFC42D.DLL等庫支持,而Release版本需要MFC42.DLL支持。Release Build不對源代碼進行調試,不考慮MFC的診斷宏,使用的是MFC Release庫,編譯十對應用程序的速度進行優化,而Debug Build則正好相反,它允許對源代碼進行調試,可以定義和使用MFC的診斷宏,采用MFC Debug庫,對速度沒有優化。
一、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 等選項造成錯誤的情況比較少,而且他們的效果顯而易見,比較容易發現。
==============================================================================================================================================================================
在使用VC開發軟件的過程中,正當要享受那種興奮的時候突然發現: release與debug運行結果不一致,甚至出錯,而release又不方便調試,真的是當頭一棒啊,可是疼歸疼,問題總要解決,下面將講述一下我的兩點經驗,看看是不是其中之一:
1. 變量。
大家都知道,debug跟release在初始化變量時所做的操作是不同的,debug是將每個字節位都賦成0xcc,而release的賦值近似於隨機(我想是直接從內存中分配的,沒有初始化過,但debug為什么不是0xff或0x00或其他什么的就不得而知了)。這樣就明確了,如果你的程序中的某個變量沒被初始化就被引用,就很有可能出現異常:用作控制變量將導致流程導向不一致;用作數組下標將會使程序崩潰;更加可能是造成其他變量的不准確而引起其他的錯誤。所以在聲明變量后馬上對其初始化一個默認的值是最簡單有效的辦法,否則項目大了你找都沒地方找。代碼存在錯誤在debug方式下可能會忽略而不被察覺到,如debug方式下數組越界也大多不會出錯,在release中就暴露出來了,這個找起來就比較難了:( 還是自己多加注意吧
2. 自定義消息的消息參數。
MFC為我們提供了很好的消息機制,更增加了自定義消息,好處我就不用多說了。這也存在debug跟release的問題嗎?答案是肯定的。在自定義消息的函數體聲明時,時常會看到這樣的寫法:afx_msg LRESULT OnMessageOwn(); 當你在多線程或進程間使用了消息傳遞時就會導致無效句柄之類的錯誤。這個原因就是消息體的參數沒有添加,即應該寫成:afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); (具體原因我也不太清楚,是不是因為調用時按着默認的參數多分配了WPARAM+LPARAM的空間而破壞了應用程序的內存空間?還請高手多指點)
避免的方法:
1. 注意變量的初始化
2. 自定義消息的標准寫法
3. 使用調試語句TRACE等時使用后最好注釋掉
4. 盡量使用try - catch(...)
VERIFY 和 ASSERT 的區別:一個在Release下面可以執行,一個不可以 DEBUG和RELEASE 版本差異及調試相關問題:
內存分配問題
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出錯
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工程都進行改動。
2.另: MS BUG:MS的一份技術文檔中表明,在VC5中對於DLL的"Maximize Speed"優化選項並未被完全支持,因此這將會引起內存錯誤並導致程序崩潰。