簡介
本文介紹了IBM Rational Purify的基本概念和在不同操作系統中使用Purify對C/C++源程序中存在的內存問題進行勘察和分析,并且提供了有關的實例以便讀者在實際操作中作為參考。
回頁首
1.內存問題的原因及分類
在C/C++程序中,有關內存使用的問題是最難發現和解決的。這些問題可能導致程序莫名其妙地停止、崩潰,或者不斷消耗內存直至資源耗盡。由于C/C++語言本身的特質和歷史原因,程序員使用內存需要注意的事項較多,而且語言本身也不提供類似Java的垃圾清理機制。編程人員使用一定的工具來查找和調試內存相關問題是十分必要的。
總的說來,與內存有關的問題可以分成兩類:內存訪問錯誤和內存使用錯誤。內存訪問錯誤包括錯誤地讀取內存和錯誤地寫內存。錯誤地讀取內存可能讓你的模塊返回意想不到的結果,從而導致后續的模塊運行異常。錯誤地寫內存可能導致系統崩潰。內存使用方面的錯誤主要是指申請的內存沒有正確釋放,從而使程序運行逐漸減慢,直至停止。這方面的錯誤由于表現比較慢很難被人工察覺。程序也許運行了很久才會耗凈資源,發生問題。
1.1 內存解剖
一個典型的C++內存布局如下圖所示:
自底向上,內存中依次存放著只讀的程序代碼和數據,全局變量和靜態變量,堆中的動態申請變量和堆棧中的自動變量。自動變量就是在函數內聲明的局部變量。當函數被調用時,它們被壓入棧;當函數返回時,它們就要被彈出堆棧。堆棧的使用基本上由系統控制,用戶一般不會直接對其進行控制,所以堆棧的使用還是相對安全的。動態內存是一柄雙刃劍:它可以提供程序員更靈活的內存使用方法,而且有些算法沒有動態內存會很難實現;但是動態內存往往是內存問題存在的沃土。
1.2 內存訪問錯誤
相對用戶使用的語言,動態內存的申請一般由malloc/new來完成,釋放由free/delete完成;镜脑瓌t可以總結為:一對一,不混用。也就是說一個malloc必須對應一且唯一的free;new對應一且唯一的delete; malloc不能和delete, new不能和free對應。另外在C++中要注意delete和delete[]的區別。delete用來釋放單元變量,delete[]用來釋放數組等集聚變量。有關這方面的詳細信息可以參考[C++Adv]。
我們可以將內存訪問錯誤大致分成以下幾類:數組越界讀或寫、訪問未初始化內存、訪問已經釋放的內存和重復釋放內存或釋放非法內存。
下面的代碼集中顯示了上述問題的典型例子:
1 #include <iostream>2 using namespace std;3 int main(){4 char* str1="four";5 char* str2=new char[4]; //not enough space6 char* str3=str2;7 cout<<str2<<endl; //UMR8 strcpy(str2,str1); //ABW9 cout<<str2<<endl; //ABR10 delete str2;11 str2[0]+=2; //FMR and FMW12 delete str3; //FFM13 }
由以上的程序,我們可以看到:在第5行分配內存時,忽略了字符串終止符"\0"所占空間導致了第8行的數組越界寫(Array Bounds Write)和第9行的數組越界讀(Array Bounds Read); 在第7行,打印尚未賦值的str2將產生訪問未初始化內存錯誤(Uninitialized Memory Read);在第11行使用已經釋放的變量將導致釋放內存讀和寫錯誤(Freed Memory Read and Freed Memory Write);最后由于str3和str2所指的是同一片內存,第12行又一次釋放了已經被釋放的空間 (Free Freed Memory)。
這個包含許多錯誤的程序可以編譯連接,而且可以在很多平臺上運行。但是這些錯誤就像定時炸彈,會在特殊配置下觸發,造成不可預見的錯誤。這就是內存錯誤難以發現的一個主要原因。
1.3 內存使用錯誤
內存使用錯誤主要是指內存泄漏,也就是指申請的動態內存沒有被正確地釋放,或者是沒有指針可以訪問這些內存。這些小的被人遺忘的內存塊占據了一定的地址空間。當系統壓力增大時,這些越來越多的小塊將最終導致系統內存耗盡。內存使用錯誤比內存訪問錯誤更加難以發現。這主要有兩點原因:第一,內存使用錯誤是"慢性病",它的癥狀可能不會在少數、短時間的運行中體現;第二,內存使用錯誤是因為"不做為"(忘記釋放內存)而不是"做錯"造成的。這樣由于忽略造成的錯誤在檢查局部代碼時很難發現,尤其是當系統相當復雜的時候。
文章來源于領測軟件測試網 http://www.kjueaiud.com/