malloc()和free()是c中兩個非持怨В基本的函數(shù),但這種最基本的東西往往都是特別復雜的纯续。
malloc和free的原形如下:
void *malloc(unsigned int num_bytes);
void free(void *ptr);
在c的標準中并沒有定義這兩個函數(shù)的具體實現(xiàn)随珠,在我們最常用的gcc中,malloc使用的是ptmalloc的實現(xiàn)猬错,最早由Doug Lea完成窗看,并被Wolfram Gloger改進以支持多線程。
malloc的具體實現(xiàn)過于復雜倦炒,這里就不具體展開了显沈。
總體上說,ptmalloc的內存管理是基于內存池的逢唤,而它的內存來源有兩種:
1 通過brk()獲得
2 通過mmap()匿名映射獲得
這兩種獲得內存的方式分別對應于進程地址空間的不同部分构罗。64位linux進程的默認內存布局如下:
對于其中的Heap區(qū)域,就是我們通常意義上的堆空間智玻,這部分內存將通過brk()獲得遂唧。brk()的原理非常簡單,只涉及指針的上下移動吊奢,也就是只分配虛擬地址空間盖彭,而不分配物理內存,當實際訪問到此內存時页滚,將觸發(fā)page fault異常由操作系統(tǒng)完成物理內存的分配召边。由于操作簡單,所以brk的效率非常高裹驰。
而另一個Memory Mapping Region區(qū)域隧熙,簡稱mmap區(qū)域,將通過操作系統(tǒng)的mmap()函數(shù)獲得幻林。mmap區(qū)域不僅可以提供內存的分配贞盯,還可以映射文件,比如通過mmap打開文件沪饺,或者加載so文件等等躏敢。
mmap函數(shù)的原形如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
其中的flags,在使用mmap分配內存時整葡,設置為MAP_ANONYMOUS件余,也就是匿名映射。當使用mmap()映射內存時,操作系統(tǒng)默認將對這塊內存執(zhí)行清0操作啼器,因此相比于brk旬渠,mmap的效率相對較低。
基于內存池的內存管理端壳,本質上都是把變長的內存分配轉換為定長的內存分配坟漱。因為只有定長,才可以復用更哄。
ptmalloc的內存池結構大致如下:
可以看出芋齿,內存的分配將根據(jù)實際的大小,選擇定長塊成翩。比如請求的內存是23字節(jié)觅捆,那么將分配一塊24字節(jié)的內存,而請求的如果是25字節(jié)麻敌,那么將分配一塊32字節(jié)的內存(對于大于512字節(jié)的數(shù)據(jù)栅炒,統(tǒng)一存儲在large bin中)。具體的分配策略就不展開了术羔。
每一種大小的內存赢赊,都組成了一個一個的隊列,在這些隊列里維護了一個雙向鏈表级历,將所有的小塊內存串聯(lián)起來释移。每一個小塊內存(chunk)的結構如下:
上面的這個結構是一塊在使用中的內存的狀態(tài)。其中的mem部分寥殖,則是返回給用戶的void*指針位置玩讳。而最開始的兩個結構:size of previous chunk,和 size of chunk嚼贡,則用于維護全局內存的鏈表熏纯。
之所以說是全局內存的鏈表,是由于內存分配時是先向系統(tǒng)請求一個比較大的內存塊(64位系統(tǒng)一般為64Mb)粤策,之后從這64Mb內存中切出用戶需要的大小分配給用戶樟澜。而為了維護分配出去的內存塊之間的關系,通過前兩個結構來使所有內存塊構成一個大的鏈表叮盘,當回收內存時秩贰,通過這個全局鏈表,將所有空閑內存組合起來熊户,還給操作系統(tǒng)萍膛,其中有3個標記位:A,M嚷堡,P,P標識前一塊內存是否空閑。
下面的結構就是一塊空閑內存的狀態(tài):
相比于使用中的狀態(tài)蝌戒,空閑部分的內存增加了4個新的結構(Forward pointer to next chunk in list 等串塑,其中fd_nextsize和bk
_nextsize只存在于large bin中),這4個結構用于維護每個定長內存隊列的雙向鏈表結構北苟,這個鏈表的存在主要是為了分配時查找內存時足夠便利桩匪,可以基本上保證分配內存時的平均復雜度維持在O(1)。
在有了前面所有的介紹之后友鼻,可以總體上描述一下malloc和free的基本流程:
當用戶向ptmalloc請求內存時:
1 首先查找定長內存分配池傻昙,如果查找到則返回
2 如果沒有空閑內存可供使用,則向操作系統(tǒng)申請一塊64Mb的內存彩扔,從中切出用戶需要的內存妆档,返回
當用戶調用free釋放內存時:
1 直接將內存放入適當?shù)亩ㄩL內存池隊列
2 如果觸發(fā)了一定的條件,則將所有空閑內存合并虫碉,如果滿足釋放條件贾惦,將內存全部還給操作系統(tǒng)
當然了,上面的描述中省略了太多的細節(jié)敦捧。比如什么時候走brk什么時候走mmap须板, 再比如當請求的內存大于一個闕值時,ptmalloc將會變成一個mmap的簡單封裝兢卵,還有觸發(fā)內存歸還操作系統(tǒng)的條件等等习瑰。
不過已經足夠回答題目中的問題了:因為malloc的時候記錄了大小。
這里還可以得出另一個結論:由于malloc的時候記錄了大量的狀態(tài)秽荤,所以在頻繁使用malloc分配小內存時杰刽,會造成大量的內存浪費。舉例來說王滤,當反復malloc(1)時贺嫂,每一次分配的內存在32字節(jié):包括size of previous chunk,size of chunk雁乡,bk_chunk_pointer第喳,fd_chunk_pointer共4個指針,合計4 * 8 = 32字節(jié)....