用戶: 密碼:     忘記密碼 | 會員註冊
  

C++ builder的除錯藝術,讓你更好瞭解C++

9.0
出處:2345軟件大全 時間:2013-01-06 人氣:1342

核心提示:這篇文章,我將從最基本的開始談起。但希望可以涉及更廣的層面,而不僅僅是為你的程序除錯(debug)。

  簡介

  這篇文章,我將從最基本的開始談起。但希望可以涉及更廣的層面,而不僅僅是為你的程序除錯(debug)。你將會看到,我認為除錯(debugging)這個字的全部意義,並不只是通過ide的內建機制來運行的。我希望在這篇小小的文章結束時,幾乎每個讀者都可以學到至少是一件新東西,並把它藏到你的兵器庫中。記住,你程序中的錯誤(bug)越少,你的最終用戶對你的程序的感覺就越好;你對錯誤(bug)的處理越好,用戶們發現錯誤(bug)時就越樂於告訴你以便你改正錯誤。好了,現在繫好安全帶,戴上護目鏡,讓我們開始一段瘋狂的路程!

  書寫乾淨的代碼

  首先而且也許是最重要的一點是書寫乾淨、可讀的代碼是極其重要的。能夠在寫完一段代碼後回顧一下並給它加上註釋來說明這段代碼用來做什麼和為什麼這麼做,將會省去你以後跟蹤代碼的無數個痛苦的小時。也許你會多花一點時間來書寫,但當你花過n小時來跟蹤那些難以捉摸的bug時,你就會同意多花點時間來讓程序代碼可讀是多麼值得了。(你本可以很容易完成除錯的)。如果沒有這麼做過,我建議你停下來,讀讀另一篇scott的精彩文章-代碼的風格(大家需要的話,將會盡快翻譯)。

  使用異常及異常處理能力

  現在進入下一步,這仍然是基於代碼的步驟。(除了在極少數的情況下,你不能老是使用系統內建的除錯器,所以知道其他可以找出這些麻煩的蟲子的辦法總是個好主意)。本步驟完全是關於如何做到,更重要的是處理好在你的窗體出現異常時系統扔給你的(產生的)錯誤。在c++標準得到認可前黑暗的舊日子裡,應用程序通常會通過返回值來發出錯誤信號(這種方法在ole和一些winapi函數中仍在使用)。很顯然,你可以很輕易的忽略這些(事實上也是經常的,我的意思是你經常檢查一個winapi函數的返回值嗎?)。

  所以他們決定….,okay,我們需要一個新的機制,一個你不能忽略的。但你可以處理,定制(自定義 customize)。異常就此出現了。想要一個特殊的錯誤類型標誌?容易的很,定義一個新的異常類型(不過是一個類,沒別的),拋出來(產生這個異常)。完了。

  例子:

2

  throw new myexception(“test exception message”);

  就這麼簡單!(當然不是很完全,我會很快加上的)。漂亮而又簡單,並且非常容易定制來滿足您的需要。okey,你會問到:“我能產生異常了,但如何處理它們?我的意思是,我想在第一時間(位置)從我的代碼中排除異常!”這當然很容易做到,實際上還很容易定制呢!標準委員會為我們定義了try {/* code */} catch (...) {/* code */ }機制,跟異常機制一樣,它完全可以定制來滿足您的需要!只需把您的執行代碼段放在try模塊中就行了,您還需要一個catch( ) 或 __finally 模塊來告訴程序(如果)得到一個異常的時候作什麼。現在就是你這麼做的好處,你定義了一個類class類型並且輸入變量來捕捉異常-通過聲明catch( )。(在前面的例子中,應該是這樣-catch(myexception &e) { /*在這裡書寫捕捉到異常後的處理代碼*/})為了讓這個系統更有力,你可以建立完整的子類繼承樹。這樣當你捕捉基類時你可以捕捉所有從這個基類繼承的異常類型(vcl中一個很好的例子就是所有的異常都是從exception類繼承而來的,所以catch(exception& e) 將捕捉所有的vcl異常,當然包也括您所產生的。但esocketerror除外,見xiphias在http://www.bytamin-c.com/ 的howto (若你不喜歡e文的話,我會盡快翻譯)。記住這個想法,我會在以後另一個步驟詳細說明)。要讓它再有力一些的話,標準委員會決定包括如下的聲明catch(…) ,沒錯括號中就是三個點。此聲明允許我們捕捉任何異常,我的意思是所有的異常。還想再有力一些?當然可以,你可以用附加的catch( )聲明,跟if..else if…的樣子差不多。這裡要牢牢記住!如果你捕捉到了一個異常類型,那麼以後就它不會被再次捕捉到了!所以先看下面的代碼…

1

  你可以看到,這裡按照 "是edbengineerror嗎? 是->處理,不是?->繼續捕捉" "是eexternalerror嗎? 是-> 處理, 不是?-> 繼續下一次捕捉" 等等… 這樣的順序排列。

  接著還有更多的內容。如果你希望對某個異常做些什麼,又不希望異常就此消失,你可以重新拋出(產生)這個異常。它將繼續向後尋找新的catch()過程來處理它。我不能說我經常這麼做。但最好應該知道,就像“拋出”一樣簡單。就是這樣,throw將帶著已經被你處理過的異常繼向後尋找另一個catch來處理它。

  最後而不是最不重要的 (這部分不包括在標準規範中,倒更像是borland專有的增加版)就是 __finally 聲明,使用一個 __finally{ } 模塊,你可以指定不管有否異常產生都將運行的代碼。這裡是清除你通過new方法分配的局部變量及將所有應該設定回正常狀態的標誌復位(例如將一個等待狀態的鼠標指針復位成正常狀態)的最方便的地方。

  太多了!休息一下吧,有空可以看一看c++builder幫助中的exception類, (所有e開頭的,你會注意到它們都是從exception類繼承來的。這也是定制你自己的異常類的好練習!) 當你回來時,我們將進入下一步旅程。

  使用記錄(logging)機制

  您不可能總是使用除錯器來除蟲,有時你沒法依靠內建除錯器的力量,所以有時你將不得不求助於其他的除錯手段來調試程序。(典型例子如:nt服務、isapi/cgi程序、實時應用程序…等等)此時您將不得不求助於我們這樣經驗豐富的程序員才會談到的老式的除錯/調試技術。例如產生使用某種記錄(logging)機制來看看程序的頭巾下面到底發生了什麼的念頭。幸運的是,有許許多多的現成的機制可以讓我們的這項工作變得容易些。這裡我將談到我所偏愛的三種方法,你也可以將您自己的方法email給我,我會考慮加入這一部分。

  okay 先說第一種,(調試/除錯輸出字串)outputdebugstring。幸運的是microsoft已經為我們實現了一個非常廣泛的調試/除錯子系統。包括實現您自己的調試/除錯記錄系統的機制。程序在一個調試/除錯進程內運行的時候,outputdebugstring將它的參數(一個c string)輸出到調試/除錯器的輸出上下文,若調試/除錯器沒有運行,outputdebugstring就被忽略。如果沒有彈出消息的時候,outputdebugstring在終端上也可以很好的運行,當你分發給客戶前別忘了移去它(通過 #ifdef debug…#endif’),程序可以運行的更快一點。“wow,又好又容易!”你也許會說“但當程序不能在調試/除錯器內運行時,該怎麼辦?”

  請牢記,這只是我的觀點,基於一種觀念的評價,我個人使用gexperts的dbugint.pas界面來調試/除錯。這是個非常優秀的獨立的小程序。如果願意,您可以將它分發給你的客戶們。如果沒有這麼做,像outputdebugstring一樣,如果沒有安裝,它實際上就什麼也不做:)(它將注意終端是否已經安裝在機器上)。要使用dbugint.pas的話很容易,將它加入你的工程並加上 #include "dbugintf.hpp"(因為是pascal文件,你必須將它加入你的工程以便c++builder編譯器生成hpp頭文件。)然後你只需使用senddebug(“要送到記錄中的字串”);或者你也許想更靈活些,還有senddebugex-增加一個消息類型參數來調用tmsgdlgtype(詳細說明參考vcl在線幫助),sendmethodenter, sendmethodexit, and sendseparator 等等(十分自解釋的名字)。只是別忘記加入必須的package包,若你打算將此終端(gdebug.exe)其給你的一些最終用戶的話。

  第三種我要指出的是,這也許是最難的選擇-實現你自己的記錄控制台。可沒有你想的那麼簡單!你也許首先會想到“扔個richedit控件在form上,將它設為只讀的,然後開始記錄,對嗎?” 錯!理論上挺好,但實踐呢,使用richedit控件來記錄將降低程序的運行速度、使內存破碎,丟失、通常會在10分鐘內使整個機器慢下來!!(要說明白為什麼得花上點時間才行,但我向你可以保證)。所以你所需要的是計劃好你的記錄機制的需要,並開始計劃一個定制控件若你想要個彩色的圖標的話。還有一個選擇,需要做點工作,但可是非常有效。就是使用一個listbox控制來記錄,並將style屬性設為lbownerdrawfixed,這樣句柄將會自繪。(這也是gexperts和它的gdebug console所做的)。要做許多工作,但哈哈,如果你想做…

  結合使用記錄機制與類的異常處理機制

  現在進入下一步:)(跟你打賭你從未意識到設置一個優秀的調試/除錯系統需要做如此多的工作!)你不用總是預料各種偶然的異常會發生什麼,而且絕大多數時候當程序經過大量的除蟲測試(盡量攻擊程序,試圖讓它崩潰)後,你根本不用擔心這些。下面這個技術,我建議任何組件開發者第一次在ide中測試一個新組件/新代碼時應該完全遵照。因為在ide中一個異常會帶來很多問題,有時甚至重啟ide也無濟於事(我自己已經這麼做了)。其實也很簡單。在您代碼的每個函數前,或者至少在所有主要的函數前後加上:

3

  (並用函數的類名及函數名代替字串中的classname和functionname)。這樣你很快很快就知道異常發生在何處,也不用你強行關閉ide啦。

  okay,是時候回顧一下了。classname()方法是如何幫助我們的?不想每次都只得到一個“exception“串就完了吧?難道是因為將e聲明為一個異常?不對。這是vcl比較酷的部分,任何從tobject繼承來的類能夠自動知道其自身的類型、其基類的類型、等等許多有趣的信息,你可以察看tobject的幫助。所以儘管我們使用的是exception &e,e.classname()將會找出我們得到的異常的實際的類名(譯者註:c++的多態性)。這些好處的代價就是可執行文件的體積更大了,幾乎所有的c++builder/delphi程序員都會發現這一點。(no pain, no gain)沒有痛苦,就沒有收穫.他們說….

  xiphias增加了tstringlist的addingline方法,savetofile方法是另一種記錄(logging)的有效形式。最後應該保證你的應用程序總是寫記錄文件(logfile),或這每次捕捉到異常時重寫記錄文件。

  處理您代碼外產生的異常

  現在的步驟是我們開始學習基於ide的除錯器之前的最後一個基於代碼的步驟。但也許在有嚴重錯誤發生時,對裝飾應用程序來說這是最重要的步驟。舉例來說,這是顯示一個包含錯誤詳細內容的對話框理想的時機。這時彈出在屏幕上的對話框可以方便最終用戶能夠向您報告錯誤。我敢保證您痛恨“oh,有個什麼框子上說在什麼地址發生了個什麼異常錯誤”這樣的報告。其實完全可以很容易的實現更好的情況,也不會限制你打算如何處理它。第一步是在你的主窗體(例如:工程的自動創建窗體列表中的第一個form)中創建一個象如下這樣的函數:

4

  然後加入合適的代碼來顯示錯誤(e->message),錯誤類型(記住e.classname(),只有此時才是它的e.classname()),和聯繫您的詳細方法及其他你想加上的任何東西。第二步當然是將它與系統掛鉤,這在c++builder裡很容易實現:

  application->onexception=applevelexceptionhandler;將上一行代碼加到form的 oncreate 事件中。不要吝嗇!你加了這一行後幾乎可以保證不會錯過任何異常,而且無論哪裡異常處理失敗時它都會出現在你的眼前!

  你的回合現在你已經得到所有你剛才學習的有用的信息了。是時候開始把它們加到你現在的工程裡去了,否則就忘掉吧,要不然,就把它變成編程習慣的一部分。這是你的自由!

  在這個系列的下個部分,我將討論內建除錯器的使用,來看看你的程序運行時都幹些什麼,如何單步跟蹤代碼、設置斷點、察看變量、和會把新手們嚇的人事不醒的所有其他有趣的工具。直到這裡,您的bugs也許只是小蟲子了吧。