第 19 講:內(nèi)存分配
前文我們已經(jīng)說了很多有關(guān) C 語言的語法了。今天我們來講一點不同的理論:堆內(nèi)存(Heap Memory)和棧內(nèi)存(Stack Memory)。
堆內(nèi)存和棧內(nèi)存的定義
堆內(nèi)存和棧內(nèi)存是兩個不同的存儲空間,由于名稱不同,所以它們各自的功能也不同。堆內(nèi)存專門用來管理我們程序使用的大量數(shù)據(jù)信息,當(dāng)程序員不用的時候,可以自己手動釋放掉(具體我們稍后再說)。不過前文我們都沒有用過堆內(nèi)存。
棧內(nèi)存指的是,當(dāng)一個函數(shù)在生成的時候,就會在棧內(nèi)存上分配指定的函數(shù)執(zhí)行信息,以及參數(shù)、變量信息。當(dāng)函數(shù)執(zhí)行完畢后,這些信息會跟著函數(shù)銷毀而自動銷毀。這些內(nèi)容是程序為我們直接完成的,無需我們手動去搞。舉個例子。
當(dāng)我們調(diào)用 f
函數(shù)時,系統(tǒng)就會為我們自動生成一個函數(shù)棧,分配其中需要用到的 x
、z
變量的空間以可以存放它們,然后生成足夠的空間可以讓我們?nèi)?zhí)行這些內(nèi)容。
當(dāng)執(zhí)行完畢整個函數(shù)(調(diào)用返回語句 return z;
)后,f
函數(shù)被銷毀,此時,f
函數(shù)棧使用的空間就會被立刻釋放掉,留給其它程序或下一次執(zhí)行某處的時候用,這樣就可以節(jié)約內(nèi)容使用。不然一直占用著,程序的使用內(nèi)存就會越來越大。
當(dāng) f
執(zhí)行完后,里面的變量的內(nèi)存就會一起被釋放,因為這些變量所處的內(nèi)存的地方,就在這個函數(shù)棧里。這就是棧內(nèi)存的方式。C 語言確實也默認所有在函數(shù)內(nèi)定義的默認類型變量都是這樣的生命周期。這些自動化處理的變量也就是前文提到過的 auto
類型變量。比如上文實例給出的 auto int z = 3
就是一個自動化處理的變量,而且一般我們都不寫這個 auto
關(guān)鍵字。
使用堆內(nèi)存
既然棧內(nèi)存這么不方便,那么可以自定義使用堆內(nèi)存嗎?是的,這也是我們接下來要提到的話題。C 語言為我們貼心地搞了一個堆內(nèi)存的體系,讓我們可以更加靈活處理很多復(fù)雜的內(nèi)存分配的機制,也可以同時去避免函數(shù)棧銷毀后同時銷毀變量本身的方式。
下面我們將開始嘗試使用堆內(nèi)存分配內(nèi)存空間,并手動釋放它們。不過我們需要介紹幾個常用函數(shù),它們在使用的時候都需要在前文 #include
指令處添加 #include <malloc.h>
頭文件導(dǎo)入指令。
malloc
函數(shù)
考慮如下代碼。
我們嘗試在堆內(nèi)存里分配了一個足夠存放 int
的空間,而這坨空間的地址就是 malloc
函數(shù)的返回值。請注意,malloc
函數(shù)是具有通用性的,所以它可以為任何其它類型進行分配內(nèi)存空間,所以此時我們需要把得到的地址強制轉(zhuǎn)換為 int *
類型后賦值給 p
,于是 p
就指向了這塊內(nèi)存了。
然后我們嘗試去比較 p
。如果 p
不為 NULL
則繼續(xù)往下執(zhí)行。這是因為 malloc
函數(shù)在分配內(nèi)存失敗時將會返回 NULL
值。當(dāng)它不為 NULL
,就說明了變量被成功分配內(nèi)存。另外,由于 NULL
和 (void *)0
等價,所以它實際上也是 0 的特殊寫法。所以 p != NULL
依然可以認為是在和 0 進行比較,所以可以省略 != 0
部分,即改寫為 p
即可:
free
函數(shù)
這個函數(shù)用于釋放掉用完的內(nèi)存。上文里如果我們已經(jīng)完成了使用 p
的操作,那么我們就可以直接釋放掉它了,所以我們把這個指針變量寫到 free
函數(shù)里當(dāng)參數(shù)就可以完成釋放了。
realloc
函數(shù)
可以從單詞上看出,realloc
就是 re-alloc。re- 前綴表示“反復(fù)”,“又”、“再一次”的意思,而 alloc 是 allocation,即分配的簡寫。所以這個函數(shù)的用法是將已經(jīng)分配過的內(nèi)存空間再一次重新分配一下,這一次可以更改內(nèi)存分配的大小。如果你對這塊內(nèi)存目前的大小不滿意,你可以調(diào)大這個內(nèi)存空間,當(dāng)然你也可以調(diào)小。它的寫法是這樣的:
寫法很簡單,第一個參數(shù)傳入需要擴容和縮容的內(nèi)存塊指針,第二個參數(shù)傳入的則是新的分配大小。在內(nèi)存里,realloc
會為我們自動往后擴容和縮容空間。當(dāng)擴容的時候,如果發(fā)現(xiàn)后續(xù)連續(xù)空間不能使用的時候,它就會自動把數(shù)據(jù)拷貝到可以連續(xù)分配到足夠內(nèi)存空間的某處上,然后把這些剛才拷貝后的備份放到相對對應(yīng)好的位置上,并返回新內(nèi)存塊的首地址。這里是 pn
得到的內(nèi)存塊重新分配后,再次賦值給 pn
。
請注意,這種賦值格式是存在嚴重的內(nèi)存 bug 的:
C6308: 'realloc' might return null pointer: assigning null pointer to 'pn', which is passed as an argument to 'realloc', will cause the original memory block to be leaked.
這段話的意思是,如果你要使用 realloc
函數(shù),就最好不要使用同一個變量來接受分配的地址。realloc
是可能返回 NULL
的,如果你用同一個變量接收的話,就可能導(dǎo)致新得到的地址是 NULL
,這樣你原本的內(nèi)存塊就找不到了,此時出現(xiàn)內(nèi)存泄漏的 bug。