1.記憶體配置設定方式(三種)
(1)從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的 整個運作期間都存在。例如全局變量,static 變量。
(2)在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有限。
(3)從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用
malloc
或
new
申請任意多少的記憶體,程式員自己負責在何時用
free
或
delete
釋放記憶體。動态記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
2.動态記憶體函數
malloc和free
C語言提供了一個動态記憶體開辟函數:
void *malloc(size_t size);
- 這個函數向記憶體申請一塊連續可用的空間,并傳回指向這塊空間的指針。
- 如果開辟成功,則傳回一個執行開辟好空間的指針。
- 如果開辟失敗,則傳回一個 NULL 指針,是以 malloc 的傳回值一定要做檢查。
- 傳回值的類型是void *,是以 malloc 函數并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
- 如果參數size為 0,malloc 的行為是标準未定義的,取決于編譯器。
C語言還提供了另外一個函數
free
,專門是用來動态記憶體的釋放和回收的,函數原型:
void free(void *ptr);
- free 函數用來釋放動态開辟的記憶體。
- 如果參數ptr指向的空間不是記憶體不是動态開辟的,那free函數的行為是未定義的。
- 如果參數ptr是 ULL指針,則函數什麼事都不做。
例:
//malloc 和 free 都聲明在 stdio.h 頭檔案中
#include <stdio.h>
int main()
{
int num = ;
scanf("%d\n", &num);
int *ptr = NULL;
ptr = (int *)malloc(num * sizeof(int));
if (NULL != ptr)//判斷ptr指針是否為空
{
int i = ;
for (i = ; i < num; i++)
*(ptr + i) = ;
}
free(ptr);//釋放ptr所指向的動态記憶體
ptr = NULL;//這是非常有必要的一步
return ;
}
calloc
C語言還提供了一個函數叫
calloc
,也用來動态記憶體配置設定,原型:
void *calloc(size_t num, size_t size);
- 函數的功能是為num個大小為 size 的元素開辟一塊空間,并把空間的每個位元組初始化為0.
- 與
的差別隻在于malloc
會在傳回值之前把申請的空間的每個位元組初始化為0.calloc
例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = calloc(, sizeof(int));
if (NULL != p);
{
//使用空間
}
free(p);
p = NULL;
return ;
}
是以如果我們隊申請的記憶體空間的内容要初始化,那麼可以很友善的使用
malloc
函數來完成任務。
realloc
-
函數的出現讓動态記憶體的管理更加靈活realloc
- 有時我們發現過去申請的空間太小了有時我們覺得申請的空間太大了,那為了合理時候的記憶體,我們一定會對記憶體的大小做靈活的調整,那realloc函數就可以做到對動态開辟記憶體大小的調整。函數原型:
void *realloc(void *ptr, size_t size);
- ptr是要調整記憶體位址。
- size調整隻後新的大小。
- 傳回值為調整之後的記憶體起始位置。
- 這個函數調整原記憶體空間大小的基礎上,還會将原來記憶體中的資料移到新的空間。
- realloc在調整記憶體時存在兩種情況:
- 【情況1】:原有空間之後有足夠大的空間,要擴充的記憶體就直接在原有記憶體之後追加空間,原有空間的資料不發生變化。
- 【情況2】:原有空間之後沒有足夠大的空間,在堆空間上找一個合适大小的連續空間來使用,這樣函數傳回的是一個新的記憶體位址。
例:
#include <stdio.h>
int main()
{
int *ptr = malloc();
if (ptr != NULL)
{
//...........
}
else
{
exit();
}
//擴充容量
int *p = NULL;
p = realloc(ptr, );
if (p != NULL)
{
ptr = p;
}
free(ptr);
return ;
}
3.常見的記憶體錯誤及其對策
發生記憶體錯誤是件非常麻煩的事情,編譯器不能自動發現這些錯誤,通常是在程式運作時才能捕捉到,而這些錯誤大多沒有明顯的症狀,時隐時現,增加了改錯的難度。
常見的記憶體錯誤及其對策如下:
(1)
記憶體配置設定未成功,卻使用了它,因為沒有意識到記憶體配置設定會不成功。
常用解決辦法是, 在使用記憶體之前檢查指針是否為 NULL。如果指針 p 是函數的參數,那麼在函數的入口 處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請記憶體,應該用if(p==NULL) 或 if(p!=NULL)進行防錯處理。
(2)
記憶體配置設定雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導緻引用初值錯誤(例如數組),記憶體的預設初值究竟是什麼并沒有統一的标準,盡管有些時候為零值,我們甯可信其無不可信其有,是以無論用何種方式建立數組,都别忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
(3)
記憶體配置設定成功并且已經初始化,但操作越過了記憶體的邊界。
例如在使用數組時經常發生下标“多 1”或者“少 1”的操作,特别是在 for 循環語句中,循環次數很容易搞錯,導緻數組操作越界。
void test()
{
int i = ;
int *p = (int *)malloc( * sizeof(int));
if (NULL == p)
{
exit();
}
for (i = ; i <= ; i++)
{
*(p + i) = i;//當i是10的時候越界通路
}
freep(p);
}
(4)
忘記了釋放記憶體,造成記憶體洩露。
含有這種錯誤的函數每被調用一次就丢失一塊記憶體。剛開始時系統的記憶體充足,你 看不到錯誤。終有一次程式突然死掉,系統出現提示:記憶體耗盡,動态記憶體的申請與釋放必須配對,程式中 malloc 與 free 的使用次數一定要相同, 否則肯定有錯誤(new/delete 同理) 。
void test()
{
int *p = (int *)malloc();
if (NULL != p)
{
*p = ;
}
}
int main()
{
test();
return ;
}
(5)
釋放了記憶體卻繼續使用它,
有三種情況:
【1】程式中的對象調用關系過于複雜,實在難以搞清楚某個對象究竟是否已經釋放了 記憶體,此時應該重新設計資料結構,從根本上解決對象管理的混亂局面。
【2】函數的 return 語句寫錯了,注意不要傳回指向“棧記憶體”的“指針”或者“引用”, 因為該記憶體在函數體結束時被自動銷毀。
【3】使用 free 或 delete 釋放了記憶體後,沒有将指針設定為 NULL。導緻産生“野指針”。
- 用 malloc 或 new 申請記憶體之後,應該立即檢查指針值是否為 NULL。 防止使用指針值為 NULL 的記憶體。
- 不要忘記為數組和動态記憶體賦初值。防止将未被初始化的記憶體作為右值使用。
- 避免數組或指針的下标越界,特别要當心發生“多 1”或者“少 1” 操作。
- 動态記憶體的申請與釋放必須配對,防止記憶體洩漏。
- 用 free 或 delete 釋放了記憶體之後,立即将指針設定為 NULL,防止 産生“野指針”