內存管理中的一些知識

2018年8月1日

20:44

1.內存分配方式

內存分配方式有三種:

  1. 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。
  2. 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
  3. 從堆上分配,亦稱動態內存分配。程序在運行的時候用 malloc 申請任意多少的內存,程序員自己負責在何時用 free 釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。  

2.常見的內存錯誤:

       1.內存分配未成功,卻使用了它。

沒有意識到內存分配會不成功。常用解決辦法是, 在使用內存之前檢查指針是否爲 NULL。如果指針 是函數的參數,那麼在函數的入口處用assert ( p ! =NULL) 進行檢查。如果是用 malloc 來申請內存,應該用 i f ( p==NULL) 或 i f ( p ! =NULL) 進行防錯處理。

     2.內存分配雖然成功,但是尚未初始化就引用它。

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以爲內存的缺省初值全爲零,導致引用初值錯誤(例如數組)。

內存的缺省初值究竟是什麼並沒有統一的標準,儘管有些時候爲零值,寧可信其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

    3.內存分配成功並且已經初始化,但操作越過了內存的邊界

例如在使用數組時經常發生下標“多 1”或者“少 1”的操作。特別是在 for 循環語句中,循環次數很容易搞錯,導致數組操作越界。

      4.忘記了釋放內存,造成內存泄露。

含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。

動態內存的申請與釋放必須配對,程序中 malloc free 的使用次數一定要相同, 否則肯定有錯誤。

      5.釋放了內存卻繼續使用它。有三種情況:

1)程序中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新設計數據結構,從根本上解決對象管理的混亂局面。

2)函數的 return 語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數體結束時被自動銷燬。

3)使用 free  釋放了內存後,沒有將指針設置爲 NULL。導致產生“野指針”。

 

注意:

a.用 ma ll oc 或 new 申請內存之後,應該立即檢查指針值是否爲 NULL。防止使用指針值爲 NULL 的內存。

b.不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存作爲右值使用。

c.避免數組或指針的下標越界,特別要當心發生“多  1”或者“少  1”操作。

d.動態內存的申請與釋放必須配對,防止內存泄漏。

e.用 free 或 delete 釋放了內存之後,立即將指針設置爲 NULL,防止產生“野指針”。

 

3.指針與數組的對比

指針和數組在不少地方可以相互替換着用,讓人產生一種錯覺,以爲兩者是等價的。數組要麼在靜態存儲區被創建(如全局數組),要麼在棧上被創建。數組名對應着(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。指針可以隨時指向任意類型的內存塊,它的特徵是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。

修改內容

char a [] = hello ;

a [ 0 ] = X;

char  * p = world;        / / 注意 p 指向常量字符串

p [ 0 ] = X;        //編譯器不能發現該錯誤

 

字符數組 a 的容量是 6 個字符,其內容爲 hello\0a 的內容可以改變,如 a[0]= ‘X’。指針 p 指向常量字符串“world”(位於靜態存儲區,內容爲 world\0),常量字符串的內容是不可以被修改的。從語法上看,編譯器並不覺得語句 p[0]= ‘X’有什麼不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。

 

內容複製與比較

不能對數組名進行直接複製與比較。下例中,若想把數組 a 的內容複製給數組 b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函數 strcpy 進行復制。同理,比較 b 和 a 的內容是否相同,不能用 i f ( b==a) 來判斷,應該用標準庫函數 s t rcmp 進行比較。

語句 p = a 並不能把 a 的內容複製指針 p,而是把 a 的地址賦給了 p。要想複製 a 的內容,可以先用庫函數 ma ll oc 爲 p 申請一塊容量爲 s t r l en( a)+1 個字符的內存,再用 s t rcpy 進行字符串複製。同理,語句 i f ( p==a) 比較的不是內容而是地址,應該用庫函數 s t rcmp 來比較。

// 數組

char a [] = " hello" ;

char b [ 10 ];

strcpy( b ,  a) ;/ / 不能用b = a ;

if ( strcmp( b ,  a) == 0 )// 不能用i f ( b == a)

// 指針

int len = strlen(a) ;

char * p =  ( char  * ) malloc( sizeof ( char) * ( len+1 )) ; strcpy( p, a) ;// 不要用 p = a ;

i f ( strcmp( p,  a) == 0 )// 不 要 用 if ( p == a)

 

4.指針參數是如何傳遞內存的

如果函數的參數是一個指針,不要指望用該指針去申請動態內存。下面的程序中Tes t 函數的語句 Get Memory( str ,  200 )並沒有使  str 獲得期望的內存,st r  依舊是 NULL

void Get Memory( char *p , int num)

{

p = ( char * ) malloc( sizeof ( char) * num) ;

}

void Test ( void)

{

char * str = NULL ;

Get Memory( str , 100 ) ;// s t r 仍然爲 NULL strcpy( str ,  " hello" ) ;// 運行錯誤

}

 

毛病出在函數 Get Memory 中。編譯器總是要爲函數的每個參數製作臨時副本,指針參數 p 的副本是 _p,編譯器使 _p = p。如果函數體內的程序修改了_p 的內容,就導致參數 p 的內容作相應的修改。這就是指針可以用作輸出參數的原因。在本例中,_p 申請了新的內存,只是把_p 所指的內存地址改變了,但是 p 絲毫未變。所以函數 Get Memory 並不能輸出任何東西。事實上,每執行一次   Get Memory  就會泄露一塊內存,因爲沒有用 free 釋放內存。

5.野指針

“野指針”不是  NULL 指針,是指向“垃圾”內存的指針。人們一般不會錯用  NULL

指針,因爲用 if 語句很容易判斷。但是“野指針”是很危險的,if 語句對它不起作用。

“野指針”的成因主要有兩種:

1)指針變量沒有被初始化。任何指針變量剛被創建時不會自動成爲 NULL 指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要麼將指針設置爲 NULL,要麼讓它指向合法的內存。例如

char * p = NULL ;

char * s t r = ( char * ) malloc( 100 ) ;

2)指針 p free 或者 delete 之後,沒有置爲 NULL,讓人誤以爲 p 是個合法的指針。

 

6.malloc/free 的使用

函數 malloc 的原型如下:

void * malloc( size_t size) ;

用 malloc 申請一塊長度爲 l eng t h 的整數類型的內存,程序如下:

Int * p = ( int * ) malloc( sizeof ( int ) * length) ;

我們應當把注意力集中在兩個要素上:“類型轉換”和“s i zeof ”。

malloc 返回值的類型是 void  *所以在調用 malloc 時要顯式地進行類型轉換,將

void * 轉換成所需要的指針類型。

malloc 函數本身並不識別要申請的內存是什麼類型,它只關心內存的總字節數。

 

函數 free 的原型如下:

void free( void * memblock ) ;

指針 p 的類型以及它所指的內存的容量事先都是知道的,語句 f ree( p) 能正確地釋放內存。如果 p 是 NULL 指針, 那麼 f ree 對 p 無論操作多少次都不會出問題。如果 p 不是 NULL 指針,那麼 f ree 對 p 連續操作兩次就會導致程序運行錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章