程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 淺談C++內存分派及變長數組的靜態分派

淺談C++內存分派及變長數組的靜態分派

編輯:關於C++

淺談C++內存分派及變長數組的靜態分派。本站提示廣大學習愛好者:(淺談C++內存分派及變長數組的靜態分派)文章只能為提供參考,不一定能成為您想要的結果。以下是淺談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));

第1、malloc 函數前往的是 void * 類型,假如你寫成:p = malloc (sizeof(int)); 則法式沒法經由過程編譯,

報錯:“不克不及將 void* 賦值給 int * 類型變量”。所以必需經由過程 (int *) 來將強迫轉換。

第2、函數的實參為 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用調試方法運轉,很快就找到了失足地位,由於在某個靜態函數中筆誤招致

在編寫穩固的辦事器法式時,這個對象尤其有效。

第二部門 數組的靜態分派及實例

1、靜態分派二維數組的普通辦法是如許:假定數組存的數據類型是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;

二  實例

<span >// malloc2d.cpp : Defines the entry point for the console application. 
// 
 
#include "stdafx.h" 
#include <iostream> 
#include <stdlib.h> 
#include <string.h> 
using namespace std; 
 
//第一種辦法,分派持續空間 
void **malloc2d(int row,int col,int size) 
{ 
  void **arr; 
  int indexsize=sizeof(void*)*row;//空出indexsize年夜小的空間用作? void*為何不可? 
  int totalsize=size*row*col; 
  arr=(void**)malloc(indexsize+totalsize); 
  if(arr!=NULL) 
  { 
    unsigned char *head;//博客中是void *head版本,但編譯都經由過程不了,改成unsigned char* 後編譯經由過程,但不明確運轉成果為何纰謬 
    head=(unsigned char *)arr+indexsize; 
    memset(arr,0,indexsize+totalsize); 
    for(int i=0;i<row;i++) 
      arr[i]=head+size*i*col; 
  } 
  return arr; 
} 
 
void free2d(void **arr) 
{ 
  if(arr!=NULL) 
    free(arr); 
} 
 
 
 
//第二中辦法,分派持續空間,C++的完成版, 
template <typename T> 
T **darray_new(int row, int col) 
{ 
  int size=sizeof(T); 
  void **arr=(void **) malloc(sizeof(void *) * row + size * row * col); 
  if (arr != NULL) 
  { 
    unsigned char * head; 
    head=(unsigned char *) arr + sizeof(void *) * row; 
    for (int i=0; i<row; ++i) 
    { 
      arr[i]= head + size * i * col; 
      for (int j=0; j<col; ++j) 
        new (head + size * (i * col + j)) T;//這一句比擬成心思,想想為何? 
    } 
  } 
  return (T**) arr; 
} 
 
template <typename T> 
void darray_free(T **arr, int row, int col)//留意要一個一個delete了,蛋疼,不外關於自界說的數據類型,很有需要 
{ 
  for (int i=0; i<row; ++i) 
    for (int j=0; j<col; ++j) 
      arr[i][j].~T();//這是甚麼玩藝兒?!模板析構?由於應用了new?所以用析構函數的delete? 
  if (arr != NULL) 
    free((void **)arr); 
} 
 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  //一維數組靜態分派 
  //int n; 
  //cin>>n; 
  ////int *p=new int[n];//一維數組靜態分派辦法一 
  //int *p=(int*)malloc(n*sizeof(int));//一維數組靜態分派辦法二 
  //for(int i=0;i<n;i++) 
  // cin>>p[i]; 
  //cout<<endl; 
  //for(int i=0;i<n;i++) 
  // cout<<p[i]<<" "; 
 
  //二維變長數組的靜態分派,自己愛好這類辦法,固然空間不持續,但異樣可以停止p[i][j]的尋址,為何博客中特地寫下面引見的函數來完成還沒找到太好的來由 
  //int n; 
  //cin>>n; 
  //int *p[2]; 
  //p[0]=new int[n]; 
  //p[1]=new int[n+1]; 
  //for(int i=0;i<n;i++) 
  // cin>>p[0][i]; 
  //cout<<&p[0]<<"   "<<&p[1]<<endl;//p[0],p[1]是持續的 
  //cout<<&p[0]<<"   "<<&p[0][0]<<"   "<<&p[0][1]<<endl;//p[0]!=p[0][0],但p[0][0],p[0][1]是持續的 
 
 
  ////C版本的,分派持續空間 
  //int**m=(int**)malloc2d(5,5,sizeof(int)); 
  //int i,j; 
  //for( i=0;i<5;i++)              //void* 泛型指針,有待分析 
  // for( j=0;j<5;j++) 
  //   m[i][j]=0; 
  //for( i=0;i<5;i++) 
  //{ 
  // for( j=0;j<5;j++) 
  //   cout<<m[i][j]<<" "; 
  // cout<<endl; 
  //} 
  //free2d((void**)m); 
 
 
  int** m=darray_new<int>(5,5);//留意模板函數怎樣完成的 <int>! 
  int i,j; 
  for( i=0;i<5;i++) 
    for( j=0;j<5;j++) 
      m[i][j]=1; 
  for( i=0;i<5;i++) 
  { 
    for( j=0;j<5;j++) 
      cout<<m[i][j]<<" "; 
    cout<<endl; 
  } 
  darray_free(m,5,5); 
  return 0; 
} 
</span> 

以上這篇淺談C++內存分派及變長數組的靜態分派就是小編分享給年夜家的全體內容了,願望能給年夜家一個參考,也願望年夜家多多支撐。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved