C++內存分配及變長數組的動態分配


//------------------------------------------------------------------------------------------------

第一部分 C++內存分配

//------------------------------------------------------------------------------------------------

一。關於內存

 1、內存分配方式

  內存分配方式有三種:

  (1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在

。例如全局變量,static變量。

  (2)在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存

儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。

  (3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自

己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。

   2.內存使用錯誤
      發生內存錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程序運行時才能捕捉到。

而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時用戶怒氣沖沖地把你找來,程序卻沒有

發生任何問題,你一走,錯誤又發作了。 常見的內存錯誤及其對策如下:
       * 內存分配未成功,卻使用了它。

  編程新手常犯這種錯誤,因為他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查

指針是否為NULL。如果是用malloc或new來申請內存,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。

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

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

錯誤(例如數組)。 內存的缺省初值究竟是什么並沒有統一的標准,盡管有些時候為零值,我們寧可信其無不

可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

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

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

錯,導致數組操作越界。

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

  含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次

程序突然死掉,系統出現提示:內存耗盡。

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

(new/delete同理)。

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

  (1)程序中的對象調用關系過於復雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新

設計數據結構,從根本上解決對象管理的混亂局面。

  (2)函數的return語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,因為該內存在函

數體結束時被自動銷毀。

  (3)使用free或delete釋放了內存后,沒有將指針設置為NULL。導致產生“野指針”。

  【規則1】用malloc或new申請內存之后,應該立即檢查指針值是否為NULL。防止使用指針值為NULL的內存

  【規則2】不要忘記為數組和動態內存賦初值。防止將未被初始化的內存作為右值使用。

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

  【規則4】動態內存的申請與釋放必須配對,防止內存泄漏。

  【規則5】用free或delete釋放了內存之后,立即將指針設置為NULL,防止產生“野指針”。

 
二. 詳解new,malloc,GlobalAlloc
    
 1.  new

  new和delete運算符用於動態分配和撤銷內存的運算符

new用法:

          1>     開辟單變量地址空間

               1)new int;  //開辟一個存放數組的存儲空間,返回一個指向該存儲空間的地址.int *a = new

int 即為將一個int類型的地址賦值給整型指針a. 

               2)int *a = new int(5) 作用同上,但是同時將整數賦值為5

          2>    開辟數組空間

               一維: int *a = new int[100];開辟一個大小為100的整型數組空間

         一般用法: new 類型 [初值]

delete用法:

          1> int *a = new int;

               delete a;   //釋放單個int的空間

          2>int *a = new int[5];

               delete [] a; //釋放int數組空間

          要訪問new所開辟的結構體空間,無法直接通過變量名進行,只能通過賦值的指針進行訪問.

          用new和delete可以動態開辟,撤銷地址空間.在編程序時,若用完一個變量(一般是暫時存儲的數組),

下次需要再用,但卻又想省去重新初始化的功夫,可以在每次開始使用時開辟一個空間,在用完后撤銷它.

2.  malloc
  原型:extern void *malloc(unsigned int num_bytes); 
  用法:#i nclude <malloc.h>或#i nclude <stdlib.h> 
  功能:分配長度為num_bytes字節的內存塊 
  說明:如果分配成功則返回指向被分配內存的指針,否則返回空指針NULL。 
  當內存不再使用時,應使用free()函數將內存塊釋放。 
  malloc的語法是:指針名=(數據類型*)malloc(長度),(數據類型*)表示指針. 
說明:malloc 向系統申請分配指定size個字節的內存空間。返回類型是 void* 類型。void* 表示未確定類型

的指針。C,C++規定,void* 類型可以強制轉換為任何其它類型的指針。

malloc()函數的工作機制 
  malloc函數的實質體現在,它有一個將可用的內存塊連接為一個長長的列表的所謂空閑鏈表。調用malloc

函數時,它沿連接表尋找一個大到足以滿足用戶請求所需要的內存塊。然后,將該內存塊一分為二(一塊的大

小與用戶請求的大小相等,另一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給用戶,並

將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閑鏈上。到

最后,空閑鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段,那么空閑鏈上可能沒有可以

滿足用戶要求的片段了。於是,malloc函數請求延時,並開始在空閑鏈上翻箱倒櫃地檢查各內存片段,對它們

進行整理,將相鄰的小空閑塊合並成較大的內存塊。
 
和new的不同
從函數聲明上可以看出。malloc 和 new 至少有兩個不同: new 返回指定類型的指針,並且可以自動計算所需

要大小。比如:
int *p;
p = new int; //返回類型為int* 類型(整數型指針),分配大小為 sizeof(int);
或:
int* parr;
parr = new int [100]; //返回類型為 int* 類型(整數型指針),分配大小為 sizeof(int) * 100;
而 malloc 則必須由我們計算要字節數,並且在返回后強行轉換為實際類型的指針。
int* p;
p = (int *) malloc (sizeof(int));
第一、malloc 函數返回的是 void * 類型,如果你寫成:p = malloc (sizeof(int)); 則程序無法通過編譯,

報錯:“不能將 void* 賦值給 int * 類型變量”。所以必須通過 (int *) 來將強制轉換。
第二、函數的實參為 sizeof(int) ,用於指明一個整型數據需要的大小。如果你寫成:
int* p = (int *) malloc (1);
代碼也能通過編譯,但事實上只分配了1個字節大小的內存空間,當你往里頭存入一個整數,就會有3個字節無

家可歸,而直接“住進鄰居家”!造成的結果是后面的內存中原有數據內容全部被清空。


3.  GlobalAlloc
 
   VC中關於GlobalAlloc,GlobalLock,GlobalUnLock

調用GlobalAlloc函數分配一塊內存,該函數會返回分配的內存句柄。 
調用GlobalLock函數鎖定內存塊,該函數接受一個內存句柄作為參數,然后返回一個指向被鎖定的內存塊的指

針。 您可以用該指針來讀寫內存。 
調用GlobalUnlock函數來解鎖先前被鎖定的內存,該函數使得指向內存塊的指針無效。 
調用GlobalFree函數來釋放內存塊。您必須傳給該函數一個內存句柄。
  
GlobalAlloc 
說明 
分配一個全局內存塊 
返回值 
Long,返回全局內存句柄。零表示失敗。會設置GetLastError 
參數表 
參數 類型及說明 
wFlags Long,對分配的內存類型進行定義的常數標志,如下所示: 
             GMEM_FIXED 分配一個固定內存塊 
             GMEM_MOVEABLE 分配一個可移動內存塊 
             GMEM_DISCARDABLE 分配一個可丟棄內存塊 
             GMEM_NOCOMPACT 堆在這個函數調用期間不進行累積 
             GMEM_NODISCARD 函數調用期間不丟棄任何內存塊 
             GMEM_ZEROINIT 新分配的內存塊全部初始化成零 
dwBytes Long,要分配的字符數

  GlobalLock  
函數功能描述:鎖定一個全局的內存對象,返回指向該對象的第一個字節的指針
函數原型:
LPVOID GlobalLock( HGLOBAL hMem )
參數:
hMem:全局內存對象的句柄。這個句柄是通過GlobalAlloc或GlobalReAlloc來得到的
返回值:
調用成功,返回指向該對象的第一個字節的指針
調用失敗,返回NULL,可以用GetLastError來獲得出錯信息
注意:
調用過GlobalLock鎖定一塊內存區后,一定要調用GlobalUnlock來解鎖
  
  GlobalUnlock
函數功能描述:解除被鎖定的全局內存對象
函數原型:BOOL GlobalUnlock( HGLOBAL hMem );
參數:hMem:全局內存對象的句柄
返回值:
非零值,指定的內存對象仍處於被鎖定狀態
0,函數執行出錯,可以用GetLastError來獲得出錯信息,如果返回NO_ERROR,則表示內存對象已經解鎖了
注意:    這個函數實際上是將內存對象的鎖定計數器減一,如果計數器不為0,則表示執行過多個GlobalLock

函數來對這個內存對象加鎖,需要對應數目的GlobalUnlock函數來解鎖。如果通過GetLastError函數返回錯誤

碼為ERROR_NOT_LOCKED,則表示未加鎖或已經解鎖。

  示例:
// Malloc memory
hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, nSize);
// Lock memory
pMem = (BYTE *) GlobalLock(hMem);
..................
// Unlock memory
GlobalUnlock(hMem);
GlobalFree(hMem);

三 總結

靈活自由是C/C++語言的一大特色,而這也為C/C++程序員出了一個難題。當程序越來越復雜時,內存的管理也

會變得越加復雜,稍有不慎就會出現內存問 題。內存泄漏是最常見的內存問題之一。內存泄漏如果不是很嚴重

,在短時間內對程序不會有太大的影響,這也使得內存泄漏問題有很強的隱蔽性,不容易被發現。 然而不管內

存泄漏多么輕微,當程序長時間運行時,其破壞力是驚人的,從性能下降到內存耗盡,甚至會影響到其他程序

的正常運行。另外內存問題的一個共同特點 是,內存問題本身並不會有很明顯的現象,當有異常現象出現時已

時過境遷,其現場已非出現問題時的現場了,這給調試內存問題帶來了很大的難度。

 下載Windows Debug 工具, http://www.microsoft.com/whdc/devtools/debugging/default.mspx
安裝后,使用其中的gflags.exe工具打開PageHeap,
gflags -p /enable MainD.exe /full
重新使用VS用調試方式運行,很快就找到了出錯位置,因為在某個靜態函數中筆誤導致

在編寫穩定的服務器程序時,這個工具尤為有用。

//------------------------------------------------------------------------------------------------

第二部分 數組的動態分配及實例

//------------------------------------------------------------------------------------------------

一 

動態分配二維數組的一般方法是這樣:假設數組存的數據類型是int
int **p=NULL; 
p=new int*[nWidth];
    if (!p){
        return NULL;
    }
    for (int j=0;j<nWidth;j++){
        p[j]=new int[nHeight];
        if (!p[j]){
            return NULL;
        }
    }
這段代碼淺顯易懂,先分配第1維,在循環分配第2維。假設二維數組是3×2的,每一句運行完后的內存情況如圖所示(方格表示內存,xx表示隨機數。下面是內存地址。當然,這個地址是個示意,事實不會分配到那的。):
第一句完后分配了3個內存單元


循環分配后,注意下面3段內存是不連續的。這樣用下表p[n][m]操作數組沒問題,如果整塊內存操作就會有問題了。

原意是想把下面的3塊6個內存單元清0,可是事與願違,把從p開始后面6個內存單元清0了,p[]不能用了。p后面只有3個已分配的內存單元,卻要操作6個,另外3個是未知區域。清了后面虛線的3塊未知區域,這就很危險了,可能導致程序崩潰。
這樣分配的內存需要循環釋放。

對這個方法有一改進,如下:

int **p=NULL; 
   p=new int *[nWidth];
if (!p){
        return NULL;
    }
    p[0]=new int[nWidth*nHeight];
if (!p[0]){
   delete[] p;
        return NULL;
    }
    ZeroMemory(p[0],nWidth*nHeight*sizeof(int));
    for (int i=1;i<nWidth;i++){
        p[i]=p[i-1]+nHeight;
    }

這段代碼解決了分配的空間不連續的問題。每一句運行完后的內存情況如圖所示:

第一句和上面一樣。

這6個內存單元是一次分配的,所以連續。


這個二維數組的數據首地址是p[0],p是第2維的索引首地址。所以如果要對二維數組進行整體的內存(緩沖區 buffer)操作,要以p[0]為操作對象的首地址。

到此,索引與對應的數據地址關聯上了。這個二維數組既可以通過下表p[][]來操作,又可以操作緩沖區。操作緩沖區的函數比如memcpy,cfile的writehuge和readhuge使用起來很方便,省去了2次循環的麻煩。

至於釋放,不必循環釋放。因為new了2次,所以只需delete2次就行了:
if(!p){
   return;
}
    delete []p[0];
    p[0]=NULL;
    delete[] p;
    p=NULL;

 

(可參考 http://hi.baidu.com/jiaon/item/52017c5a145debcfd2e10c52)

 

二  實例

[cpp]  view plain  copy
 
 print?
    1. <span style="font-size:14px;">// malloc2d.cpp : Defines the entry point for the console application.  
    2. //  
    3.   
    4. #include "stdafx.h"  
    5. #include <iostream>  
    6. #include <stdlib.h>  
    7. #include <string.h>  
    8. using namespace std;  
    9.   
    10. //第一種方法,參考http://blog.csdn.net/blind20/article/details/5214507,分配連續空間  
    11. void **malloc2d(int row,int col,int size)  
    12. {  
    13.     void **arr;  
    14.     int indexsize=sizeof(void*)*row;//空出indexsize大小的空間用作? void*為什么不行?  
    15.     int totalsize=size*row*col;  
    16.     arr=(void**)malloc(indexsize+totalsize);  
    17.     if(arr!=NULL)  
    18.     {  
    19.         unsigned char *head;//博客中是void *head版本,但編譯都通過不了,改成unsigned char* 后編譯通過,但不明白運行結果為什么不對  
    20.         head=(unsigned char *)arr+indexsize;  
    21.         memset(arr,0,indexsize+totalsize);  
    22.         for(int i=0;i<row;i++)  
    23.             arr[i]=head+size*i*col;  
    24.     }  
    25.     return arr;  
    26. }  
    27.   
    28. void free2d(void **arr)  
    29. {  
    30.     if(arr!=NULL)  
    31.         free(arr);  
    32. }  
    33.   
    34.   
    35.   
    36. //第二中方法,分配連續空間,C++的實現版,  
    37. template <typename T>  
    38. T **darray_new(int row, int col)  
    39. {  
    40.     int size=sizeof(T);  
    41.     void **arr=(void **) malloc(sizeof(void *) * row + size * row * col);  
    42.     if (arr != NULL)  
    43.     {  
    44.         unsigned char * head;  
    45.         head=(unsigned char *) arr + sizeof(void *) * row;  
    46.         for (int i=0; i<row; ++i)  
    47.         {  
    48.             arr[i]= head + size * i * col;  
    49.             for (int j=0; j<col; ++j)  
    50.                 new (head + size * (i * col + j)) T;//這一句比較有意思,想一想為什么?  
    51.         }  
    52.     }  
    53.     return (T**) arr;  
    54. }  
    55.   
    56. template <typename T>  
    57. void darray_free(T **arr, int row, int col)//注意要一個一個delete了,蛋疼,不過對於自定義的數據類型,很有必要  
    58. {  
    59.     for (int i=0; i<row; ++i)  
    60.         for (int j=0; j<col; ++j)  
    61.             arr[i][j].~T();//這是什么玩意兒?!模板析構?因為使用了new?所以用析構函數的delete?  
    62.     if (arr != NULL)  
    63.         free((void **)arr);  
    64. }  
    65.   
    66. int _tmain(int argc, _TCHAR* argv[])  
    67. {  
    68.     //一維數組動態分配  
    69.     //int n;  
    70.     //cin>>n;  
    71.     ////int *p=new int[n];//一維數組動態分配方法一  
    72.     //int *p=(int*)malloc(n*sizeof(int));//一維數組動態分配方法二  
    73.     //for(int i=0;i<n;i++)  
    74.     //  cin>>p[i];  
    75.     //cout<<endl;  
    76.     //for(int i=0;i<n;i++)  
    77.     //  cout<<p[i]<<" ";  
    78.   
    79.     //二維變長數組的動態分配,本人喜歡這種方法,雖然空間不連續,但同樣可以進行p[i][j]的尋址,為什么博客中特意寫上面介紹的函數來實現還沒找到太好的理由  
    80.     //int n;  
    81.     //cin>>n;  
    82.     //int *p[2];  
    83.     //p[0]=new int[n];  
    84.     //p[1]=new int[n+1];  
    85.     //for(int i=0;i<n;i++)  
    86.     //  cin>>p[0][i];  
    87.     //cout<<&p[0]<<"      "<<&p[1]<<endl;//p[0],p[1]是連續的  
    88.     //cout<<&p[0]<<"     "<<&p[0][0]<<"     "<<&p[0][1]<<endl;//p[0]!=p[0][0],但p[0][0],p[0][1]是連續的  
    89.   
    90.   
    91.     ////C版本的,分配連續空間  
    92.     //int**m=(int**)malloc2d(5,5,sizeof(int));  
    93.     //int i,j;  
    94.     //for( i=0;i<5;i++)                           //void* 泛型指針,有待剖析  
    95.     //  for( j=0;j<5;j++)  
    96.     //      m[i][j]=0;  
    97.     //for( i=0;i<5;i++)  
    98.     //{  
    99.     //  for( j=0;j<5;j++)  
    100.     //      cout<<m[i][j]<<" ";  
    101.     //  cout<<endl;  
    102.     //}  
    103.     //free2d((void**)m);  
    104.   
    105.   
    106.     int** m=darray_new<int>(5,5);//注意模板函數怎么實現的 <int>!  
    107.     int i,j;  
    108.     for( i=0;i<5;i++)  
    109.         for( j=0;j<5;j++)  
    110.             m[i][j]=1;  
    111.     for( i=0;i<5;i++)  
    112.     {  
    113.         for( j=0;j<5;j++)  
    114.             cout<<m[i][j]<<" ";  
    115.         cout<<endl;  
    116.     }  
    117.     darray_free(m,5,5);  
    118.     return 0;  
    119. }  
    120. </span>  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM