1、在堆上分配內(nèi)存
堆是長度可變的連續(xù)虛擬內(nèi)存,始于進(jìn)程未初始化數(shù)據(jù)段的末尾,將堆當(dāng)前的內(nèi)存邊界稱為 "program break"。
1.1、調(diào)整 program break
改變堆的大小,其實(shí)就像命令內(nèi)核改變進(jìn)程的 program break 位置一樣,最初,program break 的位置正好位于未初始化數(shù)據(jù)段末尾之后。
?
#include?int?brk(void?*end_data_segment); void?*sbrk(intptr_t?increment);
?
brk()?會將 program break 設(shè)置為參數(shù) end_data_segment 所指定的位置,由于虛擬內(nèi)存以頁為分配單位,end_data_segment 實(shí)際會四舍五入到下一個(gè)內(nèi)存頁的邊界處:
試圖將 program break 設(shè)置為一個(gè)低于其初始值的位置時(shí),有可能導(dǎo)致無法預(yù)知的行為
program break 可以設(shè)置的額精確上限取決于一系列的因素,包括進(jìn)程中對數(shù)據(jù)段大小的資源限制,以及內(nèi)存映射、共享內(nèi)存段、共享庫的位置
sbrk()?將 program break 在原有地址上增加了從參數(shù)?increment?傳入的大小,如果調(diào)用成功?sbrk()?返回前一個(gè) program break 的地址,也就是說如果 program break 增加,那么返回值將是指向這塊新分配內(nèi)存起始位置的指針
sbrk(0)?將返回 program break 的當(dāng)前位置,對其不做改變
在 program break 的位置提升之后,程序可以訪問新分配區(qū)域內(nèi)的任何內(nèi)存地址,而此時(shí)物理內(nèi)存頁尚未分配,內(nèi)核會在進(jìn)程首次視圖訪問這些虛擬內(nèi)存地址時(shí)自動分配新的物理內(nèi)存頁。
1.2、在堆上分配內(nèi)存
?
#include?void?*malloc(size_t?size);
?
malloc()?在堆上分配?size?個(gè)字節(jié)大小的內(nèi)存,并返回指向新分配內(nèi)存起始位置處的指針,其分配的內(nèi)存未經(jīng)初始化
malloc?返回的內(nèi)存塊采用了字節(jié)對齊方式,一般是基于 8 或者 16 字節(jié)邊界來進(jìn)行內(nèi)存分配,從而能夠高效地訪問任何類型的 C 語言數(shù)據(jù)結(jié)構(gòu)
如果無法分配內(nèi)存,malloc()?將會返回?NULL,并設(shè)置?errno,雖然內(nèi)存分配失敗的可能性很小,但是還是應(yīng)該對?malloc()?返回值進(jìn)行檢查
?
#include?void?free(void?*ptr);
?
free()?函數(shù)釋放?ptr?所指向的內(nèi)存塊
一般情況下,free()?并不降低 program break 的位置,而是將這塊內(nèi)存添加到空閑內(nèi)存列表中,供后續(xù)的?malloc()?函數(shù)循環(huán)使用:
被釋放的內(nèi)存塊通常位于堆的中間,而非堆的頂部,因而降低 program break 是不可能的
最大限度地減少了程序必須執(zhí)行的?sbrk()?調(diào)用次數(shù),從而降低系統(tǒng)開銷
大多數(shù)情況下,降低 program break 的位置不會對那些分配大量內(nèi)存的程序有多少幫助,因?yàn)樗鼈兺ǔA向于持有已分配內(nèi)存或者是反復(fù)釋放和重新分配內(nèi)存
給?free()?傳遞一個(gè)?NULL?指針,那么函數(shù)將什么都不做
調(diào)用?free()?后對參數(shù)的?ptr?的任何使用,包括重新傳遞給?free()?將產(chǎn)生不可預(yù)知的結(jié)果
1.3、調(diào)用?free()?還是不調(diào)用?free()
進(jìn)程終止時(shí),其占用的所有內(nèi)存都會返還給操作系統(tǒng),這包括在堆內(nèi)存中由?malloc()?函數(shù)包內(nèi)一系列函數(shù)所分配的內(nèi)存。
雖然依靠終止進(jìn)程來自動釋放內(nèi)存對大多數(shù)程序來說是可接受的,但是基于以下原因,最好能夠在程序中顯式釋放所有分配的內(nèi)存:
顯示調(diào)用?free()?能使程序在未來修改時(shí),更具可讀性和可維護(hù)性
如果使用?malloc()?調(diào)試庫來查找內(nèi)存泄漏,那么會將任何未經(jīng)顯式釋放處理的內(nèi)存報(bào)告為內(nèi)存泄漏,這會使分析變得復(fù)雜
1.4、malloc()?和?free()?的實(shí)現(xiàn)
malloc()?的實(shí)現(xiàn)很簡單:
首先會掃描之前由?free()?所釋放的空閑內(nèi)存塊列表,以求找到尺寸大于或等于要求的一塊空閑內(nèi)存,采用的掃描策略可能有 first-fit 或者 best-fito
如果這一內(nèi)存塊的尺寸正好與要求相當(dāng),就把它直接返回給調(diào)用者,如果是一塊比較大的內(nèi)存,那么將對其進(jìn)行分割,再將一塊大小相當(dāng)?shù)膬?nèi)存返回給調(diào)用者的同時(shí),把剩余的那塊內(nèi)存塊保留在空閑列表中
如果在空閑列表中根本找不到足夠大的空閑內(nèi)存塊,那么?malloc()?將調(diào)用?sbrk()?以分配更多的內(nèi)存,為了減少對?sbrk()?的調(diào)用次數(shù),malloc()?并未只是嚴(yán)格按所需的字節(jié)數(shù)來分配內(nèi)存,而是以更大幅度(以虛擬內(nèi)存頁大小的整數(shù)倍) 來增加 program break,并將超出部分置于空閑內(nèi)存列表
malloc()?分配內(nèi)存時(shí)會多分配幾個(gè)字節(jié)用來記錄這塊內(nèi)存的大小整數(shù)值,這個(gè)整數(shù)位于內(nèi)存塊的起始處,而實(shí)際返回給調(diào)用者的內(nèi)存地址恰好位于這一長度記錄字節(jié)之后。
free()?的實(shí)現(xiàn)更為有趣:
free()?將內(nèi)存塊置于空閑列表之上
歸還的大小正是依據(jù)?malloc()?預(yù)留的整數(shù)值
當(dāng)將內(nèi)存塊置于空閑內(nèi)存列表(雙向鏈表)時(shí),free()?會使用內(nèi)存塊本身的空間來存放鏈表指針,將自身添加到列表中:
隨著對內(nèi)存不斷地釋放和重新分配,空閑列表中的空閑內(nèi)存會和已經(jīng)分配的在用內(nèi)存混雜在一起:
避免內(nèi)存分配相關(guān)問題,應(yīng)該遵循的準(zhǔn)則:
分配一塊內(nèi)存后,不要改變這塊內(nèi)存范圍外的任何內(nèi)容
釋放同一塊內(nèi)存超過一次是錯(cuò)誤的,結(jié)果是不可預(yù)知的
不是經(jīng)由?malloc?函數(shù)包中函數(shù)返回的指針,決不能在調(diào)用?free()?函數(shù)時(shí)使用
避免內(nèi)存泄漏
1.5、malloc 調(diào)試的工具和庫
glibc 提供的 malloc 調(diào)試工具:
mtrace()?和?muntrace()?函數(shù)分別在程序打開和關(guān)閉對內(nèi)存分配調(diào)用進(jìn)行跟蹤的功能。這些函數(shù)要與環(huán)境變量?MALLOC_TRACE?搭配使用,該變量定義了寫入跟蹤信息的文件名
mcheck()?和?mprobe()?函數(shù)允許對已分配內(nèi)存塊進(jìn)行一致性檢查
MALLOC_CHECK_?環(huán)境變量提供了?mcheck()?和?mprobe()?函數(shù)的功能,區(qū)別在于?MALLOC_CHECK_?無需對程序進(jìn)行修改和重新編譯,將此變量設(shè)置為不同的整數(shù)值,可以控制程序?qū)?nèi)存分配錯(cuò)誤的響應(yīng)方式:
0 :忽略錯(cuò)誤
1 :在標(biāo)準(zhǔn)錯(cuò)誤輸出診斷錯(cuò)誤
調(diào)用?abort()?來終止程序
1.6、控制和監(jiān)控 malloc 函數(shù)包
glibc 手冊介紹了一系列非標(biāo)準(zhǔn)函數(shù),可以用于監(jiān)測和控制 malloc 包中的函數(shù):
mallopy()?能修改各項(xiàng)參數(shù),以控制?malloc()?所采用的算法
mallinfo()?返回一個(gè)結(jié)構(gòu),其中包含由?malloc()?分配內(nèi)存的各種統(tǒng)計(jì)數(shù)據(jù)
堆上分配內(nèi)存的其他方法
用?calloc()?和?realloc()?分配內(nèi)存
?
#include?void?*calloc(size_t?numitems,?size_t?size);
?
numitems?指定分配對象的數(shù)量,size?指定每個(gè)對象的大小
分配成功返回這塊內(nèi)存起始處的指針,無法分配時(shí)返回?NULL
calloc()?會將已分配的內(nèi)存初始化為 0
?
#include?void?*realloc(void?*ptr,?size_t?size);
?
realloc()?用來調(diào)整(通常是增加)一塊內(nèi)存的大小,此塊內(nèi)存應(yīng)該是之前由?malloc?包中函數(shù)所分配的
ptr?是指向需要調(diào)整大小的內(nèi)存塊的指針,size?指定所需調(diào)整大小的期望值
成功時(shí)?realloc()?返回指向大小調(diào)整后內(nèi)存塊的指針,與調(diào)用之前的指針相比,兩者可能不同,如果發(fā)生錯(cuò)誤,realloc()?返回?NULL,對?ptr?指針指向的內(nèi)存塊則保持不變
realloc()?不會對額外分配的字節(jié)進(jìn)行初始化
調(diào)用?realloc(ptr,0)?等效于?free(ptr)?之后再調(diào)用?malloc(0),調(diào)用?realloc(NULL,size)?相當(dāng)于調(diào)用?malloc(size)
通常情況下,當(dāng)增大已分配內(nèi)存時(shí):
realloc()?會試圖去合并在空閑列表中緊跟其后其大小滿足要求的內(nèi)存塊
如果不存在,并且原內(nèi)存塊位于堆的頂部,那么?realloc()?將會對堆空間進(jìn)行擴(kuò)展,如果原來的內(nèi)存塊在堆的中部,且緊鄰其后的空間不足,realloc()?會分配一塊新的內(nèi)存,并且將原有的數(shù)據(jù)復(fù)制到新的內(nèi)存,這種形式更為常見,會占用大量的 CPU 資源
由于?realloc()?可能會移動內(nèi)存,對這塊內(nèi)存的后續(xù)引用就必須使用?realloc()?返回的指針
一般應(yīng)該盡量避免使用?realloc()
1.7、分配對齊的內(nèi)存
?
#include?void?*memalign(size_t?boundary,?size_t?size);
?
起始地址是?boundary?的整數(shù)倍,boundary?必須是 2 的整數(shù)次冪
memalign()?并非在所有的 UNIX 實(shí)現(xiàn)上都存在,大多數(shù)?memalign()?的其他 UNIX 實(shí)現(xiàn)要求引用?
?
#include?int?posix_memalign(void?**memptr,?size_t?alignment,?size_t?size);
?
posix_memalign()?只在少數(shù) UNIX 實(shí)現(xiàn),與?memalign()?有兩個(gè)方面不同:
已分配的內(nèi)存地址通過?memptr?返回
內(nèi)存與?alignment?參數(shù)的整數(shù)倍對齊,alignment?必須是?sizeof(void*)?與 2 的整數(shù)次冪兩者之間的乘積
出錯(cuò)時(shí)不返回 -1,而是直接返回一個(gè)錯(cuò)誤號
2、在堆棧上分配內(nèi)存
?
#include?void?*alloca(size_t?size);
?
alloca()?通過增加棧幀的大小從堆棧上分配內(nèi)存
不能也不需要調(diào)用?free()?來釋放?alloca()?分配的內(nèi)存
如果調(diào)用?alloca()?造成堆棧溢出,則程序的行為是無法預(yù)知的
不要在參數(shù)列表中調(diào)用?alloca()?,這會使得分配的堆??臻g出現(xiàn)在當(dāng)前函數(shù)參數(shù)的空間內(nèi):
?
func(x,alloca(size),z)?//@?錯(cuò)誤的示范 ?? //@?必須按下面的方式進(jìn)行 void*?y; y?=?alloca(size); func(x,y,z);
?
使用?alloca()?來分配內(nèi)存相對于?malloc()?有一定優(yōu)勢:
alloca()?分配內(nèi)存的速度要快于?malloc(),因?yàn)榫幾g器將?alloca()?作為內(nèi)聯(lián)代碼處理,而是通過直接調(diào)整堆棧指針來實(shí)現(xiàn)的
alloca()?不需要維護(hù)空閑內(nèi)存塊列表
alloca()?分配的內(nèi)存隨著棧幀的移除會自動釋放,亦即當(dāng)調(diào)用?alloca()?的函數(shù)返回之時(shí),因?yàn)楹瘮?shù)返回時(shí)所執(zhí)行的代碼會重置棧指針寄存器,使其指向前一幀的末尾
在信號處理程序中調(diào)用?longjump()?或?siglongjump()?以執(zhí)行非局部跳轉(zhuǎn)時(shí),alloca()?的作用尤其突出,此時(shí)在 "起跳" 和 "落地" 之間的函數(shù)如果使用?malloc()?則很難避免內(nèi)存泄漏
評論