C語言動態內存分配函數的實現

 在C中我們開辟內存空間有兩種方式 :

1.靜態開辟內存 :例如:

int a;int b[10];

 這種開辟內存空間的特點是
所開辟的內存是在棧中開辟的固定大小的 ,如a是4字節 ,數組b是40字節 ,並且數組在申明時必須指定其長度 , 如果是全局數組的話,內存是在編譯時分配好的,如果是局部變量數組的話,運行時在棧上靜態分配內存。不管是全局數組還是局部數組,它們都有一個特點,那就是數組大小是確定的,是代碼中寫死的。那如果我們想在程序運行時才確定一個數組的大小 , 前兩種在棧上分配內存的方法顯然是不行的 , 舉個例子 :

int n;
scanf("%d", &n);
int a[n];

這樣編寫會在編譯時出錯 , 編譯器會提醒[ ]中應為常量表達式 , 在C中定義數組時可以用的有以下幾種 ,例:

#define N 10
enum NUM{
	M=10
};
int a1[N];
int a2[10];
int a3[M];

需要註意的是 ,C中const int n =10 ; n並不能作為數組長度定義數組 , 但C++中則可以 , 
但我們對於開辟空間的需求 , 往往不限於此 , 最常見的定義數組時數組大小在程序運行時才知道的 , 靜態開辟就已經無能為力 . 當然有靜態開辟 ,肯定也有動態開辟 ,接下來我們就來看動態開辟內存空間

2.動態開辟內存 :

在C中動態開辟空間需要用到三個函數 :

malloc(), calloc(), realloc() ,這三個函數都是向堆中申請的內存空間.
在堆中申請的內存空間不會像在棧中存儲的局部變量一樣 ,函數調用完會自動釋放內存 , 需要我們手動釋放 ,就需要free()函數來完成.

下面讓我們來看看這幾個函數各自的特點, 用法, 區別, 聯系.

1.malloc()

void * malloc(size_t size)

1).malloc()函數會向堆中申請一片連續的可用內存空間
2).若申請成功 ,,返回指向這片內存空間的指針 ,若失敗 ,則會返回NULL, 所以我們在用malloc()函數開辟動態內存之後, 一定要判斷函數返回值是否為NULL.
3).返回值的類型為void*型, malloc()函數並不知道連續開辟的size個字節是存儲什麼類型數據的 ,所以需要我們自行決定 ,方法是在malloc()前加強制轉 ,轉化成我們所需類型 ,如: (int*)malloc(sizeof(int)*n).
4).如果size為0, 此行為是未定義的, 會發生未知錯誤, 取決於編譯器

具體怎麼用呢 ,舉個例子 .

int *p = NULL;
int n = 0;
scanf("%d", &n);
p = (int*)malloc(sizeof(int) * n);
if(p != NULL){
    //....需要進行的操作
}

這時就相當於創建瞭一個數組 p[n] ,這個n的值並不需要像定義一個普通數組一樣必須是常量, 可以使程序運行時得出的, 或是用戶輸入的

 2.free()

void free(void* ptr)

在堆中申請的內存空間不會像在棧中存儲的局部變量一樣 ,函數調用完會自動釋放內存 , 如果我們不手動釋放, 直到程序運行結束才會釋放, 這樣就可能會造成內存泄漏, 即堆中這片內存中的數據已經不再使用, 但它一直占著這片空間, (通俗說就是就是占著茅坑不拉屎), 所以當我們申請的動態內存不再使用時 ,一定要及時釋放 .

1).如果ptr沒有指向使用動態內存分配函數分配的內存空間,則會導致未定義的行為。
2).如果ptr是空指針,則該函數不執行任何操作。
3).此函數不會更改ptr本身的值,因此它仍指向相同(現在已經無效)的位置(內存)
4).在free()函數之後需要將ptr再置空 ,即ptr = NULL;如果不將ptr置空的話 ,後面程序如果再通過ptr會訪問到已經釋放過無效的或者已經被回收再利用的內存, 為保證程序的健壯性, 一般我們都要寫ptr = NULL; . 

註意 : free()不能重復釋放一塊內存, 如:

free(ptr);
free(ptr);

是錯的, 已經釋放過的內存不能重復釋放, 會出現內存錯誤 .

free()具體用法, 舉個例子 :

int *p = NULL;
int n = 0;
scanf("%d", &n);
p = (int*)malloc(sizeof(int) * n);
if(p != NULL){
    //....需要進行的操作
}
//操作完成 ,不再使用這片內存空間
free(p);
p = NULL;

 3.calloc()

void * calloc(size_t num,size_t size)

與malloc()函數的區別隻在於, calloc()函數會在返回地址之前將所申請的內存空間中的每個字節都初始化為0 .

1).calloc()函數功能是動態分配num個大小(字節長度)為size的內存空間 .
2).若申請成功 ,,返回指向這片內存空間的指針 ,若失敗 ,則會返回NULL, 所以我們在用calloc()函數開辟動態內存之後, 一定要判斷函數返回值是否為NULL.
3).返回值的類型為void*型, calloc()函數雖然分配num個size大小的內存空間 ,但還是不知道存儲的什麼類型數據 ,所以需要我們自行決定 ,方法是在calloc()前加強制轉 ,轉化成我們所需類型 ,如: (int*)calloc(num, sizeof(int)).
4).如果size與num有一個或都為0, 此行為是未定義的, 會發生未知錯誤, 取決於編譯器

所以如何我們對申請的內存空間的內容要求初始化,那麼可以很方便的使用calloc函數來完成這個需求。
例如 :

4.realloc()

void * realloc(void * ptr,size_t size)

realloc()函數讓動態內存管理更加靈活 .在程序運行過程中動態分配內存大小,  如果分配的太大 ,則浪費空間, 如果太小, 可能還是會出現不夠用的情況 .為瞭合理的利用內存,我們一定會對內存的大小做靈活的調整。那realloc() 函數就可以做到對動態開辟內存大小的調整(既可以往大調整, 也可以往小瞭調整) .

1).ptr為需要調整的內存地址
2).size為調整後需要的大小(字節數)
3).若調整成功, 返回值為調整大小後內存的起始位置(也就是指向調整後內存的指針), 若失敗(當沒有內存可以分配時, 一般不會出現), 則返回NULL, 所以還是要對返回值判空
4).如果ptr是空指針, 則和malloc()函數一樣作用一樣

註意 : realloc()函數在擴大內存空間時有兩種情況

1).ptr所指的內存後有足夠的內存空間用來擴展 ,如圖 :

2).ptr所指內存後沒有足夠的空間來擴展 ,如圖 : 

當第二種情況時, 若申請新的內存空間成功, 會將ptr所指向的內存中的內容拷貝到新的內存空間中, ptr所指向的內存會被釋放, 返回新得內存地址, 若不成功 ,ptr 所指內存不會被釋放, 函數返回NULL

5.小結

1).malloc()和calloc()函數用法一樣, 唯一的區別是calloc()會對所申請內存的每個字節初始化為0

2).malloc(), calloc(), realloc()申請的內存不再使用時 ,一定要用free()釋放 ,否則會造成內存泄漏

3).p = realloc(ptr, size)函數返回值不為空時, 釋放內存時不需寫free(ptr) ,隻需寫free(p) 

到此這篇關於C語言動態內存分配函數的實現的文章就介紹到這瞭,更多相關C語言動態內存分配函數內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: