回覆列表
  • 1 # IT劉小虎

    在C語言程式開發中,程式設計師常常會把一個複雜的任務拆分成若干個較為簡單的子模組,這些子模組可以看做是複雜任務的各個組成部分。因此,程式設計師將子模組逐個完成後,就可以將其像“積木”一樣搭建起來,進而解決複雜任務。

    之所以要這麼做,是因為若直接編寫C語言程式碼一次性解決複雜任務,往往會讓整個程式碼“揉作一團”,不僅開發時容易引入 bug,而且後期維護起來也比較痛苦。想象一下,若將任務拆分成若干個子模組,以後發現問題時,可能只需要修改某個子模組就可以了。但是若沒有這麼做,查詢和解決問題就必須對全部程式碼下手了。哪一種開發風格維護起來方便,相信題主自然明白。

    將任務拆分成子模組後,每個子模組常常被封裝成一個C語言函式,所以,最後的“堆積木”其實就是呼叫各個C語言函式。不過,每一個子模組都有可能得到正常結果,也有可能得到異常結果,這通常用C語言函式的返回值區分。在“堆積木”階段呼叫各個函式時,應該根據被呼叫函式的返回值做不同的處理。

    例如,某個子模組負責計算使用者輸入數字的 log(對數) 值時,如果使用者輸入的是正數,則該子模組能夠得到正常的結果。但如果使用者輸入的是負數,子模組顯然就無法得到正常結果了。

    cond() 函式產出 0~10 的隨機數,如果隨機數小於 5 就返回 -1(模擬異常結果),否則返回 0(模擬正常結果)。fun1() 函式和 fun2() 函式都會根據 cond() 函式的返回值做一些進一步的工作(上面的C語言程式碼略過了“進一步工作”)。在 main() 函式中“堆積木”呼叫 fun1() 和 fun2() 函式時,使用了 if 語句判斷它們的返回值,並且根據返回值做了不同的處理。

    現在編譯這段C語言程式並執行,得到如下結果:

    從輸出結果可以發現,C語言程式輸出了“cond is false”(模擬異常)。我們往往不希望程式輸出異常結果,所以看到異常結果後,就需要知道為什麼會出現這個結果。

    適當的輸出資訊有利於定位異常

    檢視C語言原始碼,發現程式輸出異常結果是因為 main() 函數里的 if(!fun1() && !fun2()) 為假,但是無論 fun1() 還是 fun2() 返回 -1,都會導致 if 條件表示式為假,這麼看來, main() 函式呼叫 fun1() 和 fun2() 函式的方式就不太合適了,因為到這裡我們已經無法繼續追蹤異常原因了。似乎 main() 函式這麼寫更合適,相關C語言程式碼如下,請看:

    編譯修改後的C語言程式碼並執行,得到如下結果:

    這次我們就知道異常輸出是哪個函式導致的了,不過僅呼叫兩個函式就寫了這麼多行極有可能用不到的錯誤提示程式碼,太麻煩了,如果其他地方也需要用到類似的呼叫,就更麻煩了,有沒有更方便的方法呢?我們嘗試將錯誤提示資訊塞入 fun1() 和 fun2() 函式試試,如下修改 fun1() 和 fun2() 函式的程式碼:

    現在使用修改之前的 main() 函式如下:

    編譯並執行這段C語言程式碼並執行,得到如下輸出:

    這樣一來,我們既能根據輸出推斷異常是由哪個函式導致的,也能儘可能的保持C語言程式碼的簡潔性。不過程式碼還是有一點點囉嗦:

    這兩句輸出僅有 fun1 和 fun2 是不同的,但是我們卻需要完整的寫兩遍幾乎一樣的語句,而且以後萬一需要修改,還需要兩處都修改,一來麻煩,二來容易出錯。能不能避免這種情況呢?

    使用__FUNCTION__,__LINE__,__FILE__等關鍵字

    在C語言程式的編譯階段,編譯器會將__FUNCTION__,__LINE__,__FILE__這幾個關鍵字解釋為“所在函式名”,“所在行號”,“所在檔名”。所以有了這幾個關鍵字,我們就沒有必要再手動輸入函式名了,針對本節提到的例子,完全可以使用上一節介紹的 define 宏定義:

    編譯並執行這段C語言程式碼,得到如下結果:

    可以看出,程式不僅把異常的函式名輸出了,還把該函式所在的檔名(t.c) 以及行號(line:26, line:41)輸出了,這樣的除錯資訊看起來非常舒服,在大型專案開發中,實用性很強。

    類似的除錯宏還有TIMEDATE等,就不一一演示了。

    小結

    本節討論了在C語言程式開發中,複雜任務常被拆分成多個子模組並一一封裝為函式,這些函式可能有正常處理結果,也有可能有異常處理結果,所以本節討論了輸出基本除錯資訊對定位問題的重要性,並在最後介紹了幾種C語言程式開發常用的除錯宏,這些宏在大型專案開發中實用性很強。

  • 中秋節和大豐收的關聯?
  • 如何擺脫離婚後的焦慮?