讓你的C++程式碼變的更加健壯

發表於2011-11-02

介紹

在實際的專案中,當專案的程式碼量不斷增加的時候,你會發現越來越難管理和跟蹤其各個元件,如其不善,很容易就引入BUG。因此,我們應該掌握一些能讓我們程式更加健壯的方法。

這篇文章提出了一些建議,能有引導我們寫出更加健壯的程式碼,以避免產生災難性的錯誤。即使、因為其複雜性和專案團隊結構,你的程式目前不遵循任何編碼規則,按照下面列出的簡單的規則可以幫助您避免大多數的崩潰情況。

背景

先來介紹下作者開發一些軟體(CrashRpt),你可以http://code.google.com/p/crashrpt/網站上下載原始碼。CrashRpt 顧名思義軟體崩潰記錄軟體(庫),它能夠自動提交你電腦上安裝的軟體錯誤記錄。它通過乙太網直接將這些錯誤記錄傳送給你,這樣方便你跟蹤軟體問題,並及時修改,使得使用者感覺到每次釋出的軟體都有很大的提高,這樣他們自然很高興。

在分析接收的錯誤記錄的時候,我們發現採用下文介紹的方法能夠避免大部分程式崩潰的錯誤。例如:區域性變數未初始化導致陣列訪問越界,指標使用前未進行檢測(NULL)導致訪問訪問非法區域等。

我已經總結了幾條程式碼設計的方法和規則,在下文一一列出,希望能夠幫助你避免犯一些錯誤,使得你的程式更加健壯。

Initializing Local Variables (區域性變數初始化)

使用未初始化的區域性變數是引起程式崩潰的一個比較普遍的原因,例如、來看下面這段程式片段:

上面的這段程式碼存在著一個潛在的錯誤,因為沒有一個區域性變數初始化了。當你的程式碼執行的時候,這些變數將被預設負一些錯誤的數值。例如bExitResult 數值將被負為-135913245 ,szBuffer?必須以“”結尾,結果不會。因此、區域性變數初始化時非常重要的,如下正確程式碼:

注意:有人說變數初始化會引起程式效率降低,是的,確實如此,如果你確實非常在乎程式的執行效率,去除區域性變數初始化,你得想好其後果。

Initializing WinAPI Structures

許多Windows API都接受或則返回一些結構體引數,結構體如果沒有正確的初始化,也很有可能引起程式崩潰。大家可能會想起用ZeroMemory巨集或者memset()函式去用0填充這個結構體(對結構體對應的元素設定預設值)。但是大部分Windows API 結構體都必須有一個cbSIze引數,這個引數必須設定為這個結構體的大小。

看看下面程式碼,如何初始化Windows API結構體引數:

 

注意:千萬不要用ZeroMemory和memset去初始化那些包括結構體物件的結構體,這樣很容易破壞其內部結構體,從而導致程式崩潰.

這裡最好是用結構體的建構函式對其成員進行初始化.

 

Validating Function Input

在函式設計的時候,對傳入的引數進行檢測是一直都推薦的。例如、如果你設計的函式是公共API的一部分,它可能被外部客戶端呼叫,這樣很難保證客戶端傳進入的引數就是正確的。

例如,讓我們來看看這個hypotethical DrawVehicle()?函式,它可以根據不同的質量來繪製一輛跑車,這個質量數值(nDrawingQaulity )是0~100。prcDraw?定義這輛跑車的輪廓區域。

看看下面程式碼,注意觀察我們是如何在使用函式引數之前進行引數檢測:

在指標使用之前,不檢測是非常普遍的,這個可以說是我們引起軟體崩潰最有可能的原因。如果你用一個指標,這個指標剛好是NULL,那麼你的程式在執行時,將報出異常。

Initializing Function Output

如果你的函式建立了一個物件,並要將它作為函式的返回引數。那麼記得在使用之前把他複製為NULL。如不然,這個函式的呼叫者將使用這個無效的指標,進而一起程式錯誤。如下錯誤程式碼

正確的程式碼如下;

Cleaning Up Pointers to Deleted Objects

在記憶體釋放之後,無比將指標複製為NULL。這樣可以確保程式的沒有那個地方會再使用無效指標。其實就是,訪問一個已經被刪除的物件地址,將引起程式異常。如下程式碼展示如何清除一個指標指向的物件:

Cleaning Up Released Handles

在釋放一個控制程式碼之前,務必將這個控制程式碼複製偽NULL (0或則其他預設值)。這樣能夠保證程式其他地方不會重複使用無效控制程式碼。看看如下程式碼,如何清除一個Windows API的檔案控制程式碼:

 

 

下面程式碼展示如何清除File *控制程式碼:

Using delete [] Operator for Arrays

如果你分配一個單獨的物件,可以直接使用new?,同樣你釋放單個物件的時候,可以直接使用delete . 然而,申請一個物件陣列物件的時候可以使用new,但是釋放的時候就不能使用delete ,而必須使用delete[]:

或者:

Allocating Memory Carefully

有時候,程式需要動態分配一段緩衝區,這個緩衝區是在程式執行的時候決定的。例如、你需要讀取一個檔案的內容,那麼你就需要申請該檔案大小的緩衝區來儲存該檔案的內容。在申請這段記憶體之前,請注意,malloc() or new是不能申請0位元組的記憶體,如不然,將導致malloc() or new函式呼叫失敗。傳遞錯誤的引數給malloc() 函式將導致C執行時錯誤。如下程式碼展示如何動態申請記憶體:

 

 

為了進一步瞭解如何正確的分配記憶體,你可以讀下Secure Coding Best Practices for Memory Allocation in C and C++這篇文章。

Using Asserts Carefully

Asserts用語除錯模式檢測先決條件和後置條件。但當我們編譯器處於release模式的時候,Asserts在預編階段被移除。因此,用Asserts是不能夠檢測我們的程式狀態,錯誤程式碼如下:

 

看看上述的程式碼,Asserts能夠在debug模式下檢測我們的程式,在release 模式下卻不能。所以我們還是不得不用if()來這步檢測操作。正確的程式碼如下:

 

Checking Return Code of a Function

斷定一個函式執行一定成功是一種常見的錯誤。當你呼叫一個函式的時候,建議檢查下返回程式碼和返回引數的值。如下程式碼持續呼叫Windows API ,程式是否繼續執行下去依賴於該函式的返回結果和返回引數值.

 

 

Using Smart Pointers

如果你經常使用用享物件指標,如COM 介面等,那麼建議使用智慧指標來處理。智慧指標會自動幫助你維護物件引用記數,並且保證你不會訪問到被刪除的物件。這樣,不需要關心和控制介面的生命週期。關於智慧指標的進一步知識可以看看Smart Pointers – What, Why, Which??和 Implementing a Simple Smart Pointer in C++這兩篇文章。

如面是一個展示使用ATL’s CComPtr template 智慧指標的程式碼,該部分程式碼來至於MSDN。

Using == Operator Carefully

先來看看如下程式碼;

上面的程式碼是正確的,用語指標檢測。但是如果不小心用“=”替換了“==”,如下程式碼;

 

 

看看上面的程式碼,這個的一個失誤將導致程式崩潰。

這樣的錯誤是可以避免的,只需要將等號左右兩邊交換一下就可以了。如果在修改程式碼的時候,你不小心產生這種失誤,這個錯誤在程式編譯的時候將被檢測出來。

英文原文:Making Your C++ Code Robust

相關文章