C++編碼中減少內存缺陷的方法和工具
程振林,方金云,唐志敏
(中國科學(xué)院計算技術(shù)研究所,北京 100080)
摘 要:基于C++的軟件的缺陷和錯誤大部分都和內存相關(guān),預防、發(fā)現、消除代碼中和內存相關(guān)的缺陷,成為程序員編寫(xiě)、調試、維護代碼時(shí)的重要任務(wù)。該文基于“面向網(wǎng)絡(luò )海量空間信息的大型GIS”課題的工程實(shí)踐,提出和總結了如何使用C++語(yǔ)言機制、開(kāi)發(fā)環(huán)境和相關(guān)質(zhì)量保證工具來(lái)預防、發(fā)現各種編譯期、運行期內存缺陷的方法和工具。
關(guān)鍵詞:C++;內存錯誤;內存泄漏;質(zhì)量保證
Techniques and Tools of Defending Memory-related Defects in Software Coded in C++
CHENG Zhenlin, FANG Jinyun, TANG Zhimin
(Institute of Computing Technology, Chinese Academy of Sciences, Beijing 100080)
【Abstract】Most of the defects and errors in the software coded in C++ are memory-related. Based on the practice in the "network, large volumespatial information oriented GIS" project, this paper presentsthe techniques and tools to find and fix the memory problems during the coding,debugging and production release phase with the support of the C++ language mechanism, development environment and related quality-assurancetools.
【Key words】C++; Memory errors; Memory leak; Quality assurance
C++語(yǔ)言是桌面系統,尤其是系統軟件、大型應用軟件的主流開(kāi)發(fā)語(yǔ)言。C++語(yǔ)言以其靈活性著(zhù)稱(chēng),同時(shí)也更復雜。利用C++編寫(xiě)健壯的代碼,更具有挑戰性。C++允許動(dòng)態(tài)內存管理, 同時(shí)也容易導致更多和內存相關(guān)的問(wèn)題。一般而言, 除了系統設計上的缺陷, 基于C++的軟件的缺陷和錯誤大部分都和內存缺陷(主要包括內存訪(fǎng)問(wèn)錯誤和內存泄漏兩類(lèi))相關(guān)。 所以,消除代碼中的內存相關(guān)缺陷,成為程序員編寫(xiě)、調試、維護代碼中的任務(wù),也是保證軟件質(zhì)量的關(guān)鍵。
本文的工作基于“863”計劃項目“面向網(wǎng)絡(luò )海量空間信息的大型GIS”課題。該系統是基于C++/MFC編寫(xiě),開(kāi)發(fā)環(huán)境是Visual Studio .net 2003。本文基于此項目的工程實(shí)踐,總結了如何使用C++語(yǔ)言機制、開(kāi)發(fā)環(huán)境和相關(guān)質(zhì)量保證工具來(lái)預防、發(fā)現各種編譯期、運行期和內存相關(guān)的缺陷的方法和工具。
1 遵循C++相關(guān)的編碼規范和慣用法,預防缺陷
編碼規范是語(yǔ)言相關(guān)的規則,是經(jīng)過(guò)實(shí)踐總結出來(lái)的經(jīng)驗。良好的編程標準將有效地幫助開(kāi)發(fā)人員避免開(kāi)發(fā)有潛在危險的代碼。一般來(lái)說(shuō),為了減少內存缺陷,應該遵循下列編碼規則[1]:
(1)基類(lèi)或者帶有虛函數的類(lèi)應該將其析構函數聲明為虛函數。
(2)在構造函數中防止內存泄漏,在析構函數中不要拋出異常。
(3)使用對應形式的new和delete。即:用delete來(lái)釋放new申請的內存,delete[]釋放new[]申請的內存。
(4)指針在使用前必須初始化,指向動(dòng)態(tài)內存的指針在釋放后應立即置為空。
(5)如果類(lèi)構造函數中分配了資源,那么需要顯式提供拷貝構造函數和賦值操作符,并且在析構函數中釋放資源。
值得重視的是C++中的慣用法RAII。RAII核心思想是利用對象來(lái)管理資源,在對象的構造函數中獲取資源,在其析構函數中釋放資源[2]。為了保證動(dòng)態(tài)申請的內存能在即使出現異常的情況下仍能釋放,比較理想的方法是使用局部變量來(lái)管理動(dòng)態(tài)內存的所有權(ownership),就是所謂的智能指針。STL中的auto_ptr就是為解決資源所有權問(wèn)題設計的,但是缺少對引用數和數組的支持并且不能用在STL容器中。Boost庫[3]提供的智能指針相對成熟,實(shí)用價(jià)值高。其中,shared_ptr線(xiàn)程安全并且可以用在STL容器中。具體示例參考文獻[3]。
1.1 編碼規范檢查工具 CodeWizard
CodeWizard能夠對源程序直接進(jìn)行自動(dòng)掃描、分析和檢查。一旦發(fā)現違例,產(chǎn)生信息告知與哪條規則不符并作出解釋。以CodeWizard 4.3 為例,其中內置了超過(guò)500條編碼標準。CodeWizard可以選擇對于當前的工程執行哪些編碼標準。CodeWizard可以和VC++緊密集成,安裝完畢以后,VC++中有CodeWizard工具條。
1.2 代碼檢查工具 PC-Lint
PC-Lint可檢查編譯器不易發(fā)現的錯誤。PC-Lint可對100多個(gè)C庫函數進(jìn)行檢查,可以發(fā)現標準C/C++代碼中的1 000多個(gè)常見(jiàn)錯誤。要把PC-lint和Visual Studio集成在一起,需要自己配置。Jon Zyzyck提供了一個(gè)報告生成器,可以幫助完成這個(gè)工作??稍趆ttp://www.ddj.com下載。文獻[4]說(shuō)明了如何在VC++環(huán)境中集成PC-Lint。
2 利用語(yǔ)言機制、開(kāi)發(fā)環(huán)境和相關(guān)工具以預防和發(fā)現內存缺陷
發(fā)現問(wèn)題是解決問(wèn)題的前提。相對于修復內存缺陷,發(fā)現內存缺陷并準確定位導致缺陷的代碼更為費時(shí)費力。及早準確地發(fā)現內存缺陷,對于提高開(kāi)發(fā)效率非常重要。
2.1 利用斷言及早暴露內存缺陷
斷言是布爾調試語(yǔ)句,用來(lái)檢測在程序運行的時(shí)候某一條件的值是否總為真。斷言經(jīng)常用來(lái)確認函數的輸入、輸出,檢查對象的當前狀態(tài)是否合法等。 在以下的場(chǎng)景使用斷言可以幫助發(fā)現和內存非法訪(fǎng)問(wèn)相關(guān)的錯誤:
(1)驗證指針是否可讀/寫(xiě)。在函數的入口處,經(jīng)常需要驗證指針所指向的內容區域是否可讀/寫(xiě)。 通常采用assert(p!= NULL)的檢測形式。 但是,指針的值不為空并不代表指針指向了合法可讀/寫(xiě)內存。Win32 API提供了函數IsBadReadPtr、IsBadWritePtr、IsBadStringPtr、IsBadCodePtr用來(lái)檢測指針指向的內存區域是否可讀/寫(xiě)。C運行時(shí)庫提供了_CrtIs ValidPointer、_CrtIsValidHeapPointer等函數,MFC庫提供了AfxIsValidAddress、AfxIsValidString函數來(lái)完成類(lèi)似功能。
(2)對基于MFC的程序,ASSERT_VALID宏通過(guò)調用重載的AssertValid函數來(lái)確定指向CObject派生類(lèi)對象的指針是否有效。ASSERT_VALID宏主要調用了AfxIsValidAddress函數和CObject派生類(lèi)對象的AssertValid函數(參考MFC源代碼afx.h、objcore.cpp)。
2.2 利用C運行時(shí)刻庫檢查內存泄漏
VC++的C運行庫(CRT)提供了廣泛的功能,幫助用戶(hù)檢測內存泄漏。CRT提供了_CrtMemCheckPoint、_CrtDump MemoryLeaks、_CrtSetDbgFlag等函數來(lái)幫助調試內存泄漏。
對于非MFC的工程, 要開(kāi)啟有效的內存泄漏報告功能, 需要進(jìn)行如下設置:
(1)在StdAfx.h的頭部添加如下代碼并開(kāi)啟編譯器/Yu 選項:
#define _CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> #define DEBUG_NEW new(_NORMAL_BLOCK, THIS_FILE, __LINE__)
#include "stdafx.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif
聯(lián)系客服