內存分配

 

內存分配

 內存分配的基本概念

數據保存

 

  (1) 寄存器。這是最快的保存區域,因爲它位於和其他所有保存方式不同的地方:處理器內部。然而,寄存器的數量十分有限,所以寄存器是根據需要由編譯器分配。我們對此沒有直接的控制權,也不可能在自己的程序裏找到寄存器存在的任何蹤跡。

 

  (2) 堆棧。駐留於常規RAM(隨機訪問存儲器)區域,但可通過它的堆棧指針獲得處理的直接支持。堆棧指針若向下移,會創建新的內存;若向上移,則會釋放那 些內存。這是一種特別快、特別有效的數據保存方式,僅次於寄存器。

 

  (3) 堆。一種常規用途的內存池(也在RAM區域),內存堆Heap)最吸引人的地方在於編譯器不必知道要 從堆裏分配多少存儲空間,也不必知道存儲的數據要在堆裏停留多長的時間。因此,用堆保存數據時會得到更大的靈活性。要求創建一個對象時,只需用new命令 編制相關的代碼即可。執行這些代碼時,會在堆裏自動進行數據的保存。當然,爲達到這種靈活性,必然會付出一定的代價:在堆裏分配存儲空間時會花掉更長的時 間!

 

  (4) 靜態存儲。這兒的靜態Static)是指位於固定位置(儘管也在RAM裏)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。  (5) 常數存儲。常數值通常直接置於程序代碼內部。這樣做是安全的,因爲它們永遠都不會改變。有的常數需要嚴格地保護,所以可考慮將它們置入只讀存儲器(ROM)。

(6) RAM存儲。若數據完全獨立於一個程序之外,則程序不運行時仍可存在,並在程序的控制範圍之外。其中兩個最主要的例子便是流式對象固定對象。 對於流式對象,對象會變成字節流,通常會發給另一臺機器。而對於固定對象,對象保存在磁盤中。即使程序中止運行,它們仍可保持自己的狀態不變。對於這些類 型的數據存儲,一個特別有用的技巧就是它們能存在於其他媒體中。一旦需要,甚至能將它們恢復成普通的、基於RAM的對象。

 

 內存分配方式

一是:從靜態存儲區域分配,這部分內存在程序編譯的時候 就分配完成,存在於程序的整個運行過程中,例如全局變量

二是:在棧上創建,函數執行時候,函數的局部存儲單元吧  都在棧上創建,函數執行結束時候,這些存儲單元被自動釋放,棧內存分配算法內置於處理機指令集中,效率相當高,但是容量有限,一般最大1M(相對於32位系統)。

三是:在堆內存上面創建,在堆內存上面分配內存也就是我們常說的動態內存分配,利用malloc創建任意大小(32位的系統堆內存大小一般爲4G)的內存空間,用完之後,再利用free將其釋放,這裏的創建和釋放都是需要我們程序員自己來完成的,動態內存分配方便、靈活,但是問題也不少(如果你操作的不好),而且效率也遠遠低於棧內存。

 

堆和棧的區別

 

管理方式:

 

對於棧來說,都有編譯器自動管理,無需我們程序員手工控制;而對於堆來說,內存空間的創建和釋放都是由我們程序員自己手工控制的,容易memory  leak

空間大小不同

對於棧來說,一般編譯器默認的棧空間大小都爲1M,當然這個大小可以調節。而對於堆來說,不誇張地說有無限大,對於32位的操作系統來說,堆內存空間足有4G,要是再算上虛擬的,那就更多了,這麼大對於一個程序所需的內存來說是足夠大的了。

 

碎片問題:

堆內存容易產生內存碎片,而棧內存則不會,比如我們連續的分配5M的堆內存,分配了三個。接下來在程序的運行中我們又釋放了中間的5M堆內存,那麼接下來在分配堆內存時候,只要是大於5M,那麼剛纔釋放的那塊堆內存就不再會被用上。而棧之所以不會出現這種問題,是因爲棧是一個有規則的結構,有嚴格的進棧和出棧的規定,因此不會出現中間空間卻先被釋放的情況。

 

生長方向不同:

所謂生長方向就是說分配的內存是向着地址增長的方向,還是向着地址減小的方向。堆內是

屬於前者,而棧內存是屬於後者的。

 

               

 

分配方式不同:

 堆都是動態分配的,沒有靜態分配的堆內存;而棧則不同,它既有動態分配的,也有靜態

分配的。也許有人會問棧也有動態分配的?是的,有,但不常用。我們瞭解一下就行了,還

有棧的動態分配和堆的動態分配是不同的,棧動態分配後的內存由編譯器自己釋放,而堆是

由程序員釋放。

 

 

分配效率:

 我們前面也講過,棧內存的分配運算是由計算機底層支持的,具有專門的指令來處理棧的

問題,這就決定了棧的高效率。而堆不同,堆是由編程語言的函數庫提供的,實現機制相當

複雜,效率遠遠低於棧。

 

 

 常見的內存錯誤

內存錯誤一:內存分配沒有成功,卻使用了它

解決辦法:在使用之前檢查是否爲空

 

                 

內存錯誤二:內存雖然分配成功了,但是尚未初始化就用了,犯這個錯誤的原因在於一方面

沒有初始化的意識,另外誤以爲系統會將其初始化爲0,其實默認缺省值是什麼,沒有標準。

 解決辦法:記住初始化,不要嫌麻煩

 

              

內存錯誤三:內存分配成功,並且也初始化了,但是操作越界了

解決辦法:這個問題,我看只有我們細心避免了

 

內存錯誤四:忘記釋放內存,造成內存泄露

解決辦法:記住一旦分配堆內存就一定記得釋放

內存錯誤五:釋放了的內存,卻還使用,這個問題主要是因爲沒有即使的將指針賦空而導致

野指針的產生。還有指針指向的是子函數局部的棧內存,這部分內存,子函數結束時候就被

銷燬了。

解決辦法:內存一旦釋放,立刻將指針賦NULL,函數不能返回棧內存的指針。

 

內存分配規則

【規則一】 用malloc申請堆內存空間後,立即檢查是否爲空,以防止引用指針值爲空的內存

【規則二】 不要忘記爲數組和動態分配的內存空間賦初值,防止沒有被初始化的內存做了右值

【規則三】 避免數組或者指針的下標越界,特別注意多1和少1的操作

【規則四】 動態內存的申請和釋放務必要配對,以防止內存的泄露。

【規則五】 利用free釋放堆內存之後,立即將指針賦NULL,以防止野指針的產生。

 

 

9.2 內存分配函數

這裏主要講述的堆內存的動態分配和釋放,這些都是需要我們程序員自己動手完成的,功能強大的同時,也帶來了很多容易犯的錯誤,所以大家在使用動態分配內存的時候,需要特別謹慎注意。

malloc函數

malloc函數的實質體現在,它有一個將可用的內存塊連接爲一個長長的列表的所謂空閒鏈表。調用malloc函數時,它沿連接表尋找一個大到足以滿足用 戶請求所需要的內存塊。然後,將該內存塊一分爲二(一塊的大小與用戶請求的大小相等,另一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給 用戶,並將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閒鏈上。到最後,空閒鏈會被切成很多的小內存片 段,如果這時用戶申請一個大的內存片段,那麼空閒鏈上可能沒有可以滿足用戶要求的片段了。於是,malloc函數請求延時,並開始在空閒鏈上翻箱倒櫃地檢 查各內存片段,對它們進行整理,將相鄰的小空閒塊合併成較大的內存塊。如果無法獲得符合要求的內存塊,malloc函數會返回NULL指針,因此在調用 malloc動態申請內存塊時,一定要進行返回值的判斷。

 

一般形式:

指針變量 = (強制轉換)malloc(字節數);

如:int* p = (int *) malloc ( sizeof(int) * 100 );

當然也可以寫成:int *p;

                P =  (int *) malloc ( sizeof(int) * 100 );

注意:在C語言中使用malloc時,必須導入malloc.h頭文件。

 

calloc函數

 

calloc是一個C語言函數

 

函數名: calloc

 

功 能: 在內存的動態存儲區中分配n個長度爲size的連續空間,函數返回一個指向分配起始地址的指針;如果分配不成功,返回NULL

 

malloc的區別:

  calloc在動態分配完內存後,自動初始化該內存空間爲零,而malloc不初始化,裏邊數據是隨機的垃圾數據。

一般形式:指針變量 = (強制轉換)malloc(單位,字節數);

例如:

char *str;

str = (char*)calloc(10, sizeof(char));

注意使用時需加頭文件:stdlib.hmalloc.h

 

realloc函數

 

   原型extern void *realloc(void *mem_address, unsigned int newsize);

 

  語法:指針名=(數據類型*realloc(要改變內存大小的指針名,新的大小)。

  頭文件#include <stdlib.h

> 有些編譯器需要#include <alloc.h>,在TC2.0中可以使用alloc.h頭文件

 

  功能:先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而後釋放原來mem_address所指內存區域,同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。

 

  返回值:如果重新分配成功則返回指向被分配內存的指針,否則返回空指針NULL。 

注意:這裏原始內存中的數據還是保持不變的。當內存不再使用時,應使用free()函數將內存塊釋放。

例如:

int *pn=(int *)malloc(5*sizeof(int));

……

…….

pn=(int *)realloc(pn,10*sizeof(int));

 

free函數

分配的堆內存,使用結束後一定要釋放,而且釋放後還必須將其賦空,防止野指針的產生。

 

原型: void free(void *ptr)

 

  功 能: 釋放已分配的塊

 

  程序例:

 

  #include <string.h>

 

  #include <stdio.h>#include <malloc.h>

 

  int main(void)

 

  {

 

  char *str;

 

  /* allocate memory for string */

 

  str = (char *)malloc(10);

 

  /* copy "Hello" to string */

 

  strcpy(str, "Hello");

 

  /* display string */

 

  printf("String is %s\n", str);

 

  /* free memory */

free(str);

str = NULL;

 

  return 0;

}

 

 內存分配實例

大家是不是感覺長篇的理論看着頭痛了,那麼下面我們要說的知識點:指針參數如何傳遞內

 

存的?我們就不說許多了,我們來通過小例子來探討.

 

 

 

Analysis

我們前面見過類似的問題,p傳入函數getMomery()後,會生成一個副本p1,我們分配的內存空間給了p1,p卻沒有。所以p任然是NULL,所以結果段錯誤。

 

 

 

 

 

Analysis

我們還像上面一樣分析,&p傳入函數getMomery()後,會複製一個副本,我們稱之爲AA = &p;那麼我們改變*A自然就是在改變p,所以我們給*A分配了內存空間,那麼p也就分得了內存空間,其實還有一種方法也可以達到這種效果,就是利用返回值,如果你感興趣希望你自己實現以下,很多人都是從返回值的方法,慢慢過度到二級指針的。

 

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