靜態內存分配
在進程的地址空間中,代碼區、常量區、全局數據區的內存在程序啟動時就已經分配好了,它們大小固定,不能由程序員分配和釋放,只能等到程序運行結束由操作系統回收。這稱為靜態內存分配。
動態內存分配
棧區和堆區的內存在程序運行期間可以根據實際需求來分配和釋放,不用在程序剛啟動時就備足所有內存。這稱為動態內存分配。
使用靜態內存的優點是速度快,省去了向操作系統申請內存的時間,缺點就是不靈活,缺乏表現力,例如不能控制數據的作用范圍,不能使用較大的內存。而使用動態內存可以讓程序對內存的管理更加靈活和高效,需要內存就立即分配,而且需要多少就分配多少,從幾個字節到幾個GB不等;不需要時就立即回收,再分配給其他程序使用。
棧和堆的區別
棧區和堆區的管理模式有所不同:棧區內存由系統分配和釋放,不受程序員控制;堆區內存完全由程序員掌控,想分配多少就分配多少,想什么時候釋放就什么時候釋放,非常靈活。
程序啟動時會為棧區分配一塊大小適當的內存,對於一般的函數調用這已經足夠了,函數進棧出棧只是 ebp、esp 寄存器指向的變換,或者是向已有的內存中寫入數據,不涉及內存的分配和釋放。當函數中有較大的局部數組時,比如 1024*10 個元素,編譯器就會在函數代碼中插入針對棧的動態內存分配函數,這樣函數被調用時才分配內存,不調用就不分配。
我們經常聽說“棧內存的分配效率要高於堆”就是這個道理,因為大部分情況下並沒有真的分配棧內存,僅僅是對已有內存的操作。
動態內存分配函數
堆(Heap)是唯一由程序員控制的內存區域,我們常說的動態內存分配也是在這個區域。在堆上分配和釋放內存需要用到C語言標准庫中的幾個函數:malloc()、calloc()、realloc() 和 free()。
這幾個函數的具體用法在C標准庫中已經進行了講解(點擊上面鏈接查看),這里不再贅述,僅作簡單的對比,並給出一個綜合示例。
1) malloc()
原型:void* malloc (size_t size);
作用:在堆區分配 size 字節的內存空間。
返回值:成功返回分配的內存地址,失敗則返回NULL。
注意:分配內存在動態存儲區(堆區),手動分配,手動釋放,申請時空間可能有也可能沒有,需要自行判斷,由於返回的是void*,建議手動強制類型轉換。
2) calloc()
原型:void* calloc(size_t n, size_t size);
功能:在堆區分配 n*size 字節的連續空間。
返回值:成功返回分配的內存地址,失敗則返回NULL。
注意:calloc() 函數是對 malloc() 函數的簡單封裝,參數不同,使用時務必小心,第一參數是第二參數的單元個數,第二參數是單位的字節數。
3) realloc()
原型:void* realloc(void *ptr, size_t size);
功能:對 ptr 指向的內存重新分配 size 大小的空間,size 可比原來的大或者小,還可以不變(如果你無聊的話)。
返回值:成功返回更改后的內存地址,失敗則返回NULL。
4) free()
原型:void free(void* ptr);
功能:釋放由 malloc()、calloc()、realloc() 申請的內存空間。
幾點注意
-
每個內存分配函數必須有相應的 free 函數,釋放后不能再次使用被釋放的內存。
-
在分配內存時最好不要直接用數字指定內存空間的大小,這樣不利於程序的移植。因為在不同的操作系統中,同一數據類型的長度可能不一樣。為了解決這個問題,C語言提供了一個判斷數據類型長度的操作符,就是 sizeof。
-
free(p) 並不能改變指針 p 的值,p 依然指向以前的內存,為了防止再次使用該內存,建議將 p 的值手動置為 NULL。
sizeof 是一個單目操作符,不是函數,用以獲取數據類型的長度時必須加括號,例如 sizeof(int)、sizeof(char) 等。
實例
最后是一個綜合的示例:
#include <stdio.h>
#include <stdlib.h>
#define N (5)
#define N1 (7)
#define N2 (3)
int main()
{
int *ip;
int *large_ip;
int *small_ip;
if((ip = (int*)malloc(N * sizeof(int))) == NULL)
{
printf("memory allocated failed!\n");
exit(1);
}
int i;
for(i = 0; i < N; i++)
{
ip[i] = i;
printf("ip[%d] = %d\t", i, ip[i]);
}
printf("\n");
if((large_ip = (int* )realloc(ip, N1 * sizeof(int))) == NULL)
{
printf("memory allocated failed!\n");
exit(1);
}
for(i = N; i < N1; i++)
large_ip[i] = 9;
for(i = 0; i < N1; i++)
printf("large_ip[%d] = %d\t", i, large_ip[i]);
printf("\n");
if((small_ip = (int*)realloc(large_ip, N2 * sizeof(int))) == NULL)
{
printf("memory allocated failed!\n");
exit(1);
}
for(i = 0; i < N2; i++)
printf("small_ip[%d] = %d\t", i, small_ip[i]);
printf("\n");
free(small_ip);
small_ip = NULL;
system("pause");
return 0;
}
運行結果:
ip[0] = 0 ip[1] = 1 ip[2] = 2 ip[3] = 3 ip[4] = 4
large_ip[0] = 0 large_ip[1] = 1 large_ip[2] = 2 large_ip[3] = 3 large_ip[4] = 4 large_ip[5] = 9 large_ip[6] = 9
small_ip[0] = 0 small_ip[1] = 1 small_ip[2] = 2
代碼說明:
-
代碼看似很長,其實較為簡單,首先分配一個包含5個整型的內存區域,分別賦值0到4;再用realloc函數擴大內存區域以容納7個整型數,對額外的兩個整數賦值為9;最后再用realloc函數縮小內存區域,直接輸出結果(因為realloc函數會自動復制數據)。
-
這次把分配函數與驗證返回值驗證寫在了一起,為的是書寫方便,考慮到優先級問題添加了適當的括號,這種寫法較為常用,注意學習使用。
-
本例free函數只用釋放small_ip指針即可,如函數介紹中注意里提到的,另外兩個指針已被系統回收,不能再次使用。