隨筆:動態內存分配

在CPU中,CPU的職責就是執行運算而已,數據的來源則來自於內存區。但是由於CPU運算速度遠遠高於內存的讀寫速度,爲了平衡這種速度差,CPU中引入了緩存機制。但是,CPU 緩存還是不夠快,另外數據在緩存裏面的地址是不固定的,CPU 每次讀寫都要尋址也會拖慢速度。因此CPU就加入了CPU registers(寄存器)這樣一個東西。它通常被稱爲“零級緩存”。CPU在高速運算下,會優先讀寫寄存器,再由寄存器跟內存交換數據。按照存儲介質速度的不同,下圖把它們簡單做了一個排列,它們的特點是:速度越快內存越小,速度越慢內存越大。

接下來,再來分別簡單介紹一下這些存儲介質

1.寄存器:我們常常看到 32位 CPU、64位 CPU 這樣的名稱,其實指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4個字節。

2.緩存:緩存就是速度比RAM更快的RAM。具體可以參考百度百科

3.RAM: 隨機存取存儲器。分爲靜態存儲器SRAM和動態存儲器DRAM,通常靜態存儲器速度更快,但是存儲效率不如動態存儲器,而且價格更高。如需瞭解更多,具體可以參考百度百科

4.磁盤:這個用過電腦的應該都比較熟悉,磁盤分爲固態硬盤和機械硬盤。機械硬盤讀寫速度相對較慢,不過價格比較實惠。

現在來介紹一下內存空間:

一個內存空間裏面包含了四個區域:代碼區,全局變量和靜態變量區,局部變量區(即棧區)以及堆區。

代碼區和全局變量和靜態變量區就和它們的名字一樣,存儲的就是代碼和全局變量和靜態變量。

下面重點介紹一下棧區和堆區:

一個程序在運行之前,每一個代碼源文件會被編譯器編譯爲可執行文件,在編譯時期編譯器則會預先在內存區分配所需靜態內存,這些靜態內存一般被指定上面提到的前三個區域。那麼是什麼樣的變量會存儲在棧裏面呢?

基本數據類型都是存儲在棧中的(當然前提是它不是全局的,下面默認都不是全局的),基礎類型包括byte,char,short,int,float,double,long等,而數組類型要麼是在聲明期就指定它的大小,這時數組的內容都是存儲在棧中的。還有一種情況是無法在編譯期確定數組的實際大小,而是需要在程序運行時再動態分配內存,這時數組的內容就會存儲在堆中了。不過又一點需要注意的是:雖然這個數組時存在堆中,但是數組變量是存儲在棧中的,比如

int a[10] = malloc(sizeof(Int) * 10);

這個a變量是一個指針,存儲的是堆中動態分配內容的首地址。a變量是存儲在棧中的,和所有指針類型一樣,它所佔用的字節數是4個字節。值得一提的是,數組在堆中的內存分配是連續的,因此程序可以很輕鬆地通過(首地址+偏移量*下標)的方式得以訪問到數組內的任何一個元素。在C語言中,動態分配的內存需要使用free函數手動釋放掉。指向堆內存的指針變量會自動被釋放掉。除了malloc函數,還有calloc或realloc函數也能動態分配內存。它們的區別是:

malloc函數的原型是void *malloc (unsigned int size) ,其作用是在內存的動態存儲區中分配一個長度爲size的連續空間。不過只是分配而已,並不會對所分配的內存做初始化操作。分配成功的話會返回分配內存的首地址,實際使用時需要做類型強轉。如果分配內存失敗,比如內存空間不足,那麼就會返回一個NULL指針。所以在使用該函數返回的值時,需要先判斷一下內存是否分配成功。

calloc函數的原型是void *calloc(size_t n, size_t size),其作用和malloc函數差不多,區別在於它比malloc函數多做了一個初始化所分配內存區域的步驟,會全部初始化爲0。 這當然會造成額外的開銷,所以如果不需要初始化的話,可以只使用malloc函數即可。

realloc函數的原型是extern void *realloc(void *mem_address, unsigned int newsize),其作用是動態改變已分配內存的大小,第一個參數是原始分配的內存首地址,第二個參數是需要分配的內存大小。比如用malloc分配了四個int大小的數據,後來因爲某些原因需要調整該區域內存大小。就可以使用realloc函數。比如

int *a = malloc(4*sizeof(int));
int *b = realloc(a, 3*sizeof(int)); // 縮小
int *c = realloc(a, 5*sizeof(int)); // 擴大

realloc函數的第一個參數必須是來自於malloc或者calloc或者realloc函數的。realloc函數的第一個參數如果傳入的是NULL,那麼它就和malloc函數作用一樣了。realloc函數如果是縮小內存的話,返回的指針一般還是原來的指針,只不過縮小之後的內容,被剔除的內存被系統回收了,如果再訪問,可能會引起野指針異常。而如果realloc函數是擴大內存的話,它所返回的指針就不一定是原來的指針內容了。因爲內存分配的都是連續的地址空間,如果在當前地址下分配不了指定的size大小,則系統會另外開闢一段內存空間,然後將之前指針指向的內容複製到新的內存地址,並自動釋放掉原來的指針,最後返回的是新的內存地址。如果因爲某些原因,比如系統內存不足,那麼該函數將會返回一個NULL指針。所以它在使用之前也需要做判空。

總結一下:使用動態分配的內存都是使用malloc函數、calloc函數或者realloc函數分配的,它們都存儲在堆中,而指向它們的指針變量是存在棧中。動態分配的內存可能會分配失敗,所以需要做判空操作。動態分配的內存必須使用free()函數釋放,否則會造成內存泄漏。

參考鏈接:百度百科--動態內存分配Java靜態內存和動態內存解析彙編語言入門教程

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