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