C指针原理(42)-内存管理与控制
C語(yǔ)言的stdlib庫(kù)提供了內(nèi)存分配與管理的函數(shù):
1、通過調(diào)用calloc、malloc和realloc所分配的空間,如果連續(xù)調(diào)用它們,不能保證空間是順利或連續(xù)的。當(dāng)分配成功后,函數(shù)將返回一個(gè)指針,這個(gè)指針指向分配成功的空間的開始位置, 它可以被指向任意類型的對(duì)象,當(dāng)分配空間失敗后,返回NULL指針。
2、通過free函數(shù)釋放空間。
3、函數(shù)說明
(1)calloc函數(shù)
函數(shù)的原型為:
void *calloc(size_t nmemb,size_t size);
為nmemb個(gè)對(duì)象的數(shù)組分配空間,每個(gè)元素的大小為size,分配的空間所有位被初始為0。
(2)free函數(shù)
函數(shù)的原型為:
原型:extern void free(void *ptr);
釋放指針ptr所指向的的內(nèi)存空間。ptr所指向的內(nèi)存空間必須是用calloc,malloc,realloc所分配的內(nèi)存。如果ptr為NULL或指向不存在的內(nèi)存塊則不做任何操作。
釋放的空間還可以被重新分配,
(3)malloc函數(shù)
void *malloc(size_t size);
分配長(zhǎng)度為size字節(jié)的內(nèi)存塊。如果分配成功則返回指向被分配內(nèi)存的指針,否則返回空指針NULL。當(dāng)內(nèi)存不再使用時(shí),應(yīng)使用free()函數(shù)將內(nèi)存塊釋放。
(4)realloc函數(shù)
void realloc(void ptr, size_t size);
改變ptr所指內(nèi)存區(qū)域的大小為size長(zhǎng)度。如果重新分配成功則返回指向被分配內(nèi)存的指針,否則返回空指針NULL。當(dāng)內(nèi)存不再使用時(shí),應(yīng)使用free()函數(shù)將內(nèi)存塊釋放。新分配空間比空間大,并包括原空間的的內(nèi)容,但因?yàn)榉峙湫驴臻g,沒有初始化0的操作,所以新空間中除去舊空間的部分的內(nèi)容不能保證清空為零。
4、C語(yǔ)言中數(shù)據(jù)對(duì)象空間分配原理
C語(yǔ)言中的數(shù)據(jù)對(duì)象存儲(chǔ)在以下3種空間中:
(1) 程序在開始執(zhí)行前,分配 靜態(tài)存儲(chǔ)空間,并進(jìn)行初始化,如果沒有指定數(shù)據(jù)對(duì)象的初始值,則每個(gè)標(biāo)題 的值 初始化為零。這樣的數(shù)據(jù)對(duì)象在程序結(jié)束前一直存在。
(2) 程序在每一個(gè)程序塊的入口分配動(dòng)態(tài)存儲(chǔ)空間。如果沒有指定數(shù)據(jù)初始值,它的初始內(nèi)容不確定,可能上次程序使用釋放過的內(nèi)存內(nèi)容(釋放內(nèi)存本身并不清空內(nèi)存中的數(shù)據(jù),只是標(biāo)記這塊內(nèi)存操作系統(tǒng)可重新使用)。 這些數(shù)據(jù)對(duì)象在程序塊執(zhí)行完畢前一直存在。
(3) 調(diào)用calloc、malloc、realloc時(shí),程序才分配可被程序員人為操縱的存儲(chǔ)空間,僅當(dāng)調(diào)用calloc時(shí),空間才被初始化。這樣分配的數(shù)據(jù)對(duì)象要用free函數(shù)釋放。否則將生存到程序結(jié)束。
5、堆的原理
靜態(tài)存儲(chǔ)空間存在在于程序的整個(gè)執(zhí)行過程 中,動(dòng)態(tài) 存儲(chǔ)空間是后進(jìn)先出,可用棧實(shí)現(xiàn)。
動(dòng)態(tài)存儲(chǔ)空間經(jīng)常與函數(shù)調(diào)用與返回?cái)?shù)據(jù)一起使用堆棧。可被人為操縱的存儲(chǔ)空間不遵守這個(gè)規(guī)定。C語(yǔ)言的標(biāo)準(zhǔn)庫(kù)維持著叫“堆”的空間池來控制被calloc、malloc、realloc函數(shù)分配的存儲(chǔ)空間。
堆中分配的每塊內(nèi)存都應(yīng)被free函數(shù)釋放,free函數(shù)要釋放內(nèi)存塊,就意味著它必須知道釋放多大的內(nèi)存塊,然后調(diào)用該函數(shù),并沒有將需要釋放的內(nèi)存大小告訴它,因此,必須有一種數(shù)據(jù)結(jié)構(gòu)記錄每個(gè)已經(jīng)分配的內(nèi)存塊的信息,同時(shí)在堆內(nèi)存的多次釋放與申請(qǐng)過程中,必須會(huì)形成很多內(nèi)存碎片,形成很多數(shù)據(jù)對(duì)象間的小空間,減少了堆的實(shí)際可用空間。
多個(gè)內(nèi)存塊(Chunks)通常具有相同的尺寸,從內(nèi)存塊邊界地址開始,多個(gè)領(lǐng)域( Arena)將眾多內(nèi)存塊分割成較小的空間進(jìn)行分配,超大的內(nèi)存分配(huge arena)要同時(shí)占據(jù)多個(gè)連續(xù)內(nèi)存塊(Chunks)才夠用。
分配大小分為3個(gè)部分:小的、大的和巨大的,所有分配請(qǐng)求被安排到最接近的大小邊界,超大的內(nèi)存分配大于內(nèi)存塊的一半,這種分配存儲(chǔ)在單獨(dú)的紅黑樹中。對(duì)小型和大型的分配,塊分割成頁(yè),使用二伙伴(Binary-Buddy)算法作為分割算法,Binary-Buddy在分配內(nèi)存的時(shí)候,首先找到一個(gè)空閑內(nèi)存塊,接著把內(nèi)存塊不斷的進(jìn)行對(duì)半切分(切分得到的2個(gè)同樣大小的內(nèi)存塊互為伙伴),直到切出來的內(nèi)存塊剛好滿足分配需求為止,合并的時(shí)候,只有伙伴才能合并為一個(gè)新的內(nèi)存塊。
小型和大型分配通過反復(fù)將分配大小折半,最后走到一個(gè)內(nèi)存頁(yè),但只能合并的方式是分裂過程的相反操作,運(yùn)行的狀態(tài)信息作為頁(yè)面映射存儲(chǔ)為每個(gè)內(nèi)存塊(Chunks)的開始處,通過分開存儲(chǔ)這些信息,頁(yè)面只接觸它們使用的部分,同時(shí)對(duì)于超過一半的頁(yè),但沒有超過一半的內(nèi)存塊的大型分配,這種做法同樣高效和安全。
小型分配分為三個(gè)類:小、量子尺寸(quantum-spaced)和子頁(yè),根據(jù)數(shù)據(jù)類型的不同,現(xiàn)代架構(gòu)要求內(nèi)存對(duì)齊,malloc(3)要求返回適合邊界的內(nèi)存時(shí),在糟糕的情況下,對(duì)齊要求被稱為量子尺寸(通常是16字節(jié))。如下圖所示:
jemalloc分配機(jī)制包括領(lǐng)域(arena)、塊(chunk)、執(zhí)行(bin)、運(yùn)行(run)、線程緩存等部分,jemalloc使用多個(gè)分配領(lǐng)域(Arena)將內(nèi)存分而治之,以塊(chunk)作為具體進(jìn)行內(nèi)存分配的區(qū)域,塊(chunk)以頁(yè)(page)為單位進(jìn)行管理,每個(gè)塊(chunk)的前幾個(gè) 頁(yè)(page)用于存儲(chǔ)后面所有頁(yè)(page)的狀態(tài),后面的所有頁(yè)(page)則用于進(jìn)行實(shí)際的分配。執(zhí)行(bin)用來管理各個(gè)不同大小單元的分配,每個(gè)執(zhí)行(bin)通過對(duì)它對(duì)應(yīng)的運(yùn)行(run)操作來進(jìn)行分配的,一個(gè)運(yùn)行(run)實(shí)際上就是塊(chunk)里的一塊區(qū)域 ,在分配內(nèi)存時(shí)首先從線程對(duì)應(yīng)的私有緩存空間(tcache)中找。
但現(xiàn)代的處理器都是多處理器,并行計(jì)算已經(jīng)慢慢成為一種趨勢(shì),多線程運(yùn)算不可避免,為提高malloc在多線程環(huán)境的效率和安全性,增強(qiáng)多線程的伸縮性,Jason Evans 在他的《A Scalable Concurrent malloc(3) Implementation for FreeBSD》一文中提出了并發(fā)狀態(tài)的malloc機(jī)制,jemalloc使用多個(gè)分配領(lǐng)域(Arena)減少在多處理器系統(tǒng)中的線程之間的鎖競(jìng)爭(zhēng),雖然增加一些成本,但更有利于提供多線程的伸縮性。現(xiàn)代的多處理器在每個(gè)高速緩存線(per-cache-line )的基礎(chǔ)上提供了內(nèi)存的一致性視圖,如果兩個(gè)線程同時(shí)運(yùn)行在不同的處理器上,但在同一緩存線(cache-line)操縱不同的對(duì)象,那么處理器必須仲裁緩存線(cache-line)的所有權(quán)。6、分配機(jī)制
malloc函數(shù)是內(nèi)存分配機(jī)制的核心,realloc函數(shù)內(nèi)部通過調(diào)用malloc函數(shù)申請(qǐng)更大的內(nèi)存空間,calloc函數(shù)也是如此,先通過調(diào)用malloc函數(shù)申請(qǐng)空間,然后將空間初始化為0。
通常來說,在大多數(shù)操作系統(tǒng)(流行的UNIX/LINUX系統(tǒng)、MAC OS以及WINDOWS)中,malloc函數(shù)的運(yùn)作原理為:它有一個(gè)將可用的內(nèi)存塊連接為一個(gè)長(zhǎng)長(zhǎng)的列表的所謂空閑鏈表。調(diào)用malloc函數(shù)時(shí),它沿連接表尋找一個(gè)大到足以滿足用戶請(qǐng)求所需要的內(nèi)存塊。然后,將該內(nèi)存塊一分為二(一塊的大小與用戶請(qǐng)求的大小相等,另一塊的大小就是剩下的字節(jié))。接下來,將分配給用戶的那塊內(nèi)存?zhèn)鹘o用戶,并將剩下的那塊(如果有的話)返回到連接表上。調(diào)用free函 數(shù)時(shí),它將用戶釋放的內(nèi)存塊連接到空閑鏈上。到最后,空閑鏈會(huì)被切成很多的內(nèi)存碎片,當(dāng)用戶申請(qǐng)一個(gè)大的內(nèi)存片段,那么空閑鏈上沒有供分配的內(nèi)存了,malloc函數(shù)在空閑鏈上整理各內(nèi)存片段,將相鄰的小空閑塊合并成較大的內(nèi)存塊等等,最大可能得在內(nèi)存中騰出需要的空閑空間返回,如果努力失敗將返回NULL指針。
在多處理器中,內(nèi)存分配如何減少程序的多個(gè)線程的鎖競(jìng)爭(zhēng)?可采用在每個(gè)分配器中放置一個(gè)鎖,為分配器準(zhǔn)備多個(gè)領(lǐng)域,通過對(duì)線程標(biāo)識(shí)的HASH計(jì)算將各個(gè)線程分配到這些領(lǐng)域中,如下圖所示:
jemalloc使用的是比HASH更具彈性的算法將線程分派到領(lǐng)域中,在FREEBSD中,默認(rèn)情況下,單處理器使用一個(gè)領(lǐng)域,而多處理器中使用相當(dāng)于處理器4倍數(shù)量的領(lǐng)域。
當(dāng)線程分配器第一次分配或釋放內(nèi)存時(shí),被分派一個(gè)領(lǐng)域,但不是通過線程標(biāo)識(shí)的HASH,而是循環(huán)的方式,每個(gè)區(qū)域盡量保證被分派的線程數(shù)相等,沒用的HASH的原因在于,做到線程標(biāo)識(shí)符(就是線程指針)的可靠的偽隨機(jī)HASH非常困難。線程本地存儲(chǔ)(TLS)對(duì)高效實(shí)現(xiàn)循環(huán)領(lǐng)域非常重要,每個(gè)線程的領(lǐng)域需要一個(gè)存儲(chǔ)位置,在一些不支持TLS的架構(gòu)上,仍需要使用線程標(biāo)識(shí)HASH,因此使用pthreads庫(kù)的TSD替代TLS解決這一問題。
mmap函數(shù)和sbrk函數(shù)擔(dān)負(fù)malloc等分配內(nèi)存的函數(shù)向內(nèi)核申請(qǐng)內(nèi)存的任務(wù),mmap將一個(gè)文件或者其它對(duì)象映射進(jìn)內(nèi)存。文件被映射到多個(gè)頁(yè)上,如果文件的大小不是所有頁(yè)的大小之和,最后一個(gè)頁(yè)不被使用的空間將會(huì)清零,而sbrk增加程序可用數(shù)據(jù)段空間。
內(nèi)存塊(chunk)的尺寸默認(rèn)為2M,分配內(nèi)存塊時(shí),基址是尺寸的常數(shù)倍,這就是邊界對(duì)齊,從理論上講似乎對(duì)任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。各個(gè)硬件平臺(tái)對(duì)存儲(chǔ)空間的處理上有很大的不同,一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取,邊界不對(duì)齊將導(dǎo)致讀取效率下降很多。
以freebsd10.0系統(tǒng)為例,freebsd采用的是jemalloc分配機(jī)制,它能盡量減少內(nèi)存碎片,提供可伸縮的并發(fā)支持,jemalloc在2005首次被引入到freebsd的libc庫(kù)的分配器。在下圖的分析比較中,可以看到,jemalloc分配機(jī)制在眾多內(nèi)存分配機(jī)制中(最右邊的直方框),性能是最好的,
jemalloc使用多個(gè)分配領(lǐng)域(Arena)將內(nèi)存分而治之,以減少在多處理器系統(tǒng)中的線程之間的鎖競(jìng)爭(zhēng),雖然增加一些成本,但更有利于提供多線程的伸縮性。現(xiàn)代的多處理器在每個(gè)高速緩存線(per-cache-line )的基礎(chǔ)上提供了內(nèi)存的一致性視圖,如果兩個(gè)線程同時(shí)運(yùn)行在不同的處理器上,但在同一緩存線(cache-line)操縱不同的對(duì)象,那么處理器必須仲裁緩存線(cache-line)的所有權(quán)。
如下圖所示,被不同線程使用的2個(gè)分配器在物理高速緩存上共享同一根緩存線,如果幾個(gè)線程同時(shí)修改2個(gè)分配器,處理器得決定這根緩存線歸哪個(gè)線程使用。
?
這種看似合理但實(shí)質(zhì)有可能造成低效的高速緩存線路共享會(huì)導(dǎo)致嚴(yán)重的性能下降。解決這個(gè)問題的方法之一是填充分配(pad allocation),但填充分配與讓數(shù)據(jù)對(duì)象盡可能緊密的目標(biāo)背道而馳,它會(huì)引起嚴(yán)重的內(nèi)存碎片,jemalloc使用一個(gè)替代方案,依賴于多個(gè)分配領(lǐng)域(Arena)來減少問題,它讓應(yīng)用程序編寫者自己進(jìn)行填充分配(pad allocation),從而在性能的關(guān)鍵代碼、線程分配對(duì)象的代碼以及將對(duì)象傳遞給多個(gè)其他線程中,有意避免緩存線程共享機(jī)制帶來的影響。
總結(jié)
以上是生活随笔為你收集整理的C指针原理(42)-内存管理与控制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阮一峰的学习Javascript闭包(C
- 下一篇: php 的不等于符号,mysql 不等于