c語言之內存的申請malloc() 和釋放free()
1.如何使用 malloc 函數
malloc是一個函數,專門用來從堆上分配內存。使用malloc函數需要幾個要求:
內存分配給誰?
分配多大內存?
是否還有足夠內存分配?
內存的將用來存儲什么格式的數據,即內存用來做什么?
分配好的內存在哪里?
如果這五點都確定,那內存就能分配。下面先看malloc函數的原型:
|
1
|
(
void
*)malloc(
int
size)
|
看到了沒有,這里的返回類型是(void *),這是多巧妙的一個設計啊。
malloc函數的返回值是一個void類型的指針,參數為int類型數據,即申請分配的內存大小,單位是byte。內存分配成功之后,malloc函數返回這塊內存的首地址。你需要一個指針來接收這個地址。但是由於函數的返回值是void *類型的,所以必須強制轉換成你所接收的類型。也就是說,這塊內存將要用來存儲什么類型的數據。比如:
|
1
|
char
*p = (
char
*)malloc(
100
);
|
在堆上分配了100個字節內存,返回這塊內存的首地址,把地址強制轉換成char *類型后賦給char *類型的指針變量p。同時告訴我們這塊內存將用來存儲char類型的數據。也就是說你只能通過指針變量p來操作這塊內存。這塊內存本身並沒有名字,對它的訪問是匿名訪問。
上面就是使用malloc函數成功分配一塊內存的過程。但是,每次你都能分配成功嗎?
不一定。
函數同樣要注意這點:如果所申請的內存塊大於目前堆上剩余內存塊(整塊),則內存分配會失敗,函數返回NULL。注意這里說的“堆上剩余內存塊”不是所有剩余內存塊之和,因為malloc函數申請的是連續的一塊內存。既然malloc函數申請內存有不成功的可能,那我們在使用指向這塊內存的指針時,必須用if(NULL!=p)語句來驗證內存確實分配成功了。
2. 用 malloc 函數申請 0 字節內存
另外還有一個問題:用malloc函數申請0字節內存會返回NULL指針嗎?
可以測試一下,也可以去查找關於malloc函數的說明文檔。申請0字節內存,函數並不返回NULL,而是返回一個正常的內存地址。但是你卻無法使用這塊大小為0的內存。這好尺子上的某個刻度,刻度本身並沒有長度,只有某兩個刻度一起才能量出長度。對於這一點一定要小心,因為這時候if(NULL!=p)語句校驗將不起作用。
3.內存釋放
既然有分配,那就必須有釋放。不然的話,有限的內存總會用光,而沒有釋放的內存卻在空閑。與malloc對應的就是free函數了。free函數只有一個參數,就是所要釋放的內存塊的首地址。比如上例:
|
1
|
free(p);
|
free函數看上去挺狠的,但它到底作了什么呢?
其實它就做了一件事:斬斷指針變量與這塊內存的關系。
比如上面的例子,我們可以說malloc函數分配的內存塊是屬於p的,因為我們對這塊內存的訪問都需要通過p來進行。free函數就是把這塊內存和p之間的所有關系斬斷。從此p和那塊內存之間再無瓜葛。至於指針變量p本身保存的地址並沒有改變,但是它對這個地址處的那塊內存卻已經沒有所有權了。那塊被釋放的內存里面保存的值也沒有改變,只是再也沒有辦法使用了。
這就是free函數的功能。按照上面的分析,如果對p連續兩次以上使用free函數,肯定會發生錯誤。因為第一使用free函數時,p所屬的內存已經被釋放,第二次使用時已經無內存可釋放了。關於這點,我(陳正沖老師)上課時讓學生記住的是:一定要一夫一妻制,不然肯定出錯。
malloc兩次只free一次會內存泄漏;malloc一次free兩次肯定會出錯。也就是說,在程序中malloc的使用次數一定要和free相等,否則必有錯誤。這種錯誤主要發生在循環使用malloc函數時,往往把malloc和free次數弄錯了。
4.內存釋放之后
既然使用free函數之后指針變量p本身保存的地址並沒有改變,那我們就需要重新把p的值變為NULL:
|
1
|
p = NULL;
|
這個NULL就是我們前面所說的“栓野狗的樹樁”。如果你不栓起來遲早會出問題的。比如:
在free(p)之后,你用if(NULL!=p)這樣的校驗語句還能起作用嗎?
例如:
|
1
2
3
4
5
6
7
8
9
|
char
*p = (
char
*) malloc(
100
);
strcpy(p, “hello”);
free(p);
/* p 所指的內存被釋放,但是p所指的地址仍然不變*/
⋯
if
(NULL != p)
{
/* 沒有起到防錯作用*/
strcpy(p, “world”);
/* 出錯*/
}
|
釋放完塊內存之后,沒有把指針置NULL,這個指針就成為了“野指針”,也有書叫“懸垂指針”。這是很危險的,而且也是經常出錯的地方。所以一定要記住一條:free完之后,一定要給指針置NULL。
5.內存已經被釋放了,但是繼續通過指針來使用
這里一般有三種情況:
第一種:就是上面所說的,free(p)之后,繼續通過p指針來訪問內存。解決的辦法就是給p置NULL。
第二種:函數返回棧內存。這是初學者最容易犯的錯誤。比如在函數內部定義了一個數組,卻用return語句返回指向該數組的指針。解決的辦法就是弄明白棧上變量的生命周期。
第三種:內存使用太復雜,弄不清到底哪塊內存被釋放,哪塊沒有被釋放。解決的辦法是重新設計程序,改善對象之間的調用關系。
對c語言野指針的解釋
那到底什么是野指針呢?怎么去理解這個“野”呢?我們先看別的兩個關於“野”的詞:
野狗:沒有主人的狗,沒有鏈子鎖着的狗,喜歡四處咬人。
對付野狗最好的辦法就是拿條狗鏈把它拴在樹樁上,不讓它四處亂跑。
前面我們把內存比作尺子,很輕松的理解了內存。尺子上的0毫米處就是內存的0地址處,也就是NULL地址處。
在C語言中定義指針變量的同時最好初始化為NULL,用完指針free(p)之后也將指針變量的值設置為p=NULL。這樣就把p這條狗成功地栓到NULL這個樹樁上,那它就沒有機會成為野狗了。所以說NULL可以看作專門栓“野指針”(野狗)的樹樁。也就是說除了在使用時,別的時間都把指針“栓”到0地址處。這樣它就老實了。
參考:陳正沖老師的《c語言深度剖析》。
