當(dāng)前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導(dǎo)讀]當(dāng)你調(diào)用 new 和 delete 時(shí)編譯器底層到底做了哪些工作?STL 各大容器底層空間配置原理是怎樣的?STL 空間配置器到底要考慮什么?什么是內(nèi)存的配置和釋放?

1. 前言

天下大事,必作于細(xì)。

源碼之前,了無秘密。

你清楚下面這幾個(gè)問題嗎?

  • 當(dāng)你調(diào)用 new 和 delete 時(shí)編譯器底層到底做了哪些工作?

  • STL 各大容器底層空間配置原理是怎樣的?

  • STL 空間配置器到底要考慮什么?

  • 什么是內(nèi)存的配置和釋放?

這篇,我們就來回答這些問題。

2. STL 六大組件

在深入配置器之前,我們有必要了解下 STL 的背景知識:

標(biāo)準(zhǔn)模板庫(英文:Standard Template Library,縮寫:STL),是一個(gè) C++ 軟件庫。

STL 的價(jià)值在于兩個(gè)方面,就底層而言,STL 帶給我們一套極具實(shí)用價(jià)值的零部件以及一個(gè)整合的組織;除此之外,STL 還帶給我們一個(gè)高層次的、以泛型思維 (Generic Paradigm) 為基礎(chǔ)的、系統(tǒng)化的“軟件組件分類學(xué)”。

STL 提供六大組件,了解這些為接下來的閱讀打下基礎(chǔ)。

1、容器(containers):各種數(shù)據(jù)結(jié)構(gòu),如 vector, list, deque, set, map 用來存放數(shù)據(jù)。從實(shí)現(xiàn)的角度來看,STL 容器是一種 class template。

2、算法(algorithms):各種常用的算法如 sort, search, copy, erase…從實(shí)現(xiàn)角度來看,STL 算法是一種 function template。

3、迭代器(iterators):扮演容器與算法之間的膠合劑,是所謂的“泛型指針”。從實(shí)現(xiàn)角度來看,迭代器是一種將 operator *, operator ->, operator++, operator– 等指針相關(guān)操作予以重載的class template。

4、仿函數(shù)(functors):行為類似函數(shù),可以作為算法的某種策略。從實(shí)現(xiàn)角度來看,仿函數(shù)是一種重載了 operator() 的 class 或class template。

5、適配器(adapters):一種用來修飾容器或仿函數(shù)或迭代器接口的東西。例如 STL 提供的 queue 和 stack,雖然看似容器,其實(shí)只能算是一種容器適配器,因?yàn)樗鼈兊牡撞客耆柚?deque,所有操作都由底層的 deque 供應(yīng)。

6、配置器(allocator):負(fù)責(zé)空間配置與管理,從實(shí)現(xiàn)角度來看,配置器是一個(gè)實(shí)現(xiàn)了動(dòng)態(tài)空間配置、空間管理、空間釋放的 class template。

初學(xué)作圖,有點(diǎn)丑,還能看,嘿嘿

3. 何為空間配置器

3.1 為何需要先了解空間配置器?

從使用 STL 層面而言,空間配置器并不需要介紹 ,因?yàn)槿萜鞯讓佣冀o你包裝好了,但若是從 STL 實(shí)現(xiàn)角度出發(fā),空間配置器是首要理解的。

作為 STL 設(shè)計(jì)背后的支撐,空間配置器總是在默默地付出著。為什么你可以使用算法來高效地處理數(shù)據(jù),為什么你可以對容器進(jìn)行各種操作,為什么你用迭代器可以遍歷空間,這一切的一切,都有“空間配置器”的功勞。

3.2 SGI STL 專屬空間配置器

SGI STL 的空間配置器與眾不同,且與 STL 標(biāo)準(zhǔn)規(guī)范不同。

其名為 alloc,而非 allocator。

雖然 SGI 也配置了 allocatalor,但是它自己并不使用,也不建議我們使用,原因是效率比較感人,因?yàn)樗皇窃诨鶎舆M(jìn)行配置/釋放空間而已,而且不接受任何參數(shù)。

SGI STL 的每一個(gè)容器都已經(jīng)指定缺省的空間配置器是 alloc。

在 C++ 里,當(dāng)我們調(diào)用 new 和 delete 進(jìn)行對象的創(chuàng)建和銷毀的時(shí)候,也同時(shí)會(huì)有內(nèi)存配置操作和釋放操作:

這其中的 new 和 delete 都包含兩階段操作:

a. 對于 new 來說,編譯器會(huì)先調(diào)用 ::operator new 分配內(nèi)存;然后調(diào)用 Obj::Obj() 構(gòu)造對象內(nèi)容。

b. 對于 delete 來說,編譯器會(huì)先調(diào)用 Obj::~Obj() 析構(gòu)對象;然后調(diào)用? ::operator delete 釋放空間。

為了精密分工,STL allocator 決定將這兩個(gè)階段操作區(qū)分開來。

c. 對象構(gòu)造由 ::construct() 負(fù)責(zé);對象釋放由 ::destroy() 負(fù)責(zé)。

d. 內(nèi)存配置由 alloc::allocate() 負(fù)責(zé);內(nèi)存釋放由 alloc::deallocate() 負(fù)責(zé);

STL配置器定義在中,下圖直觀的描述了這一框架結(jié)構(gòu):

4. 構(gòu)造和析構(gòu)源碼

我們知道,程序內(nèi)存的申請和釋放離不開基本的構(gòu)造和析構(gòu)基本工具:construct() 和 destroy()?。

在 STL 里面,construct() 函數(shù)接受一個(gè)指針 P 和一個(gè)初始值 value,該函數(shù)的用途就是將初值設(shè)定到指針?biāo)傅目臻g上。

destroy() 函數(shù)有兩個(gè)版本,第一個(gè)版本接受一個(gè)指針,準(zhǔn)備將該指針?biāo)钢镂鰳?gòu)掉。直接調(diào)用析構(gòu)函數(shù)即可。

第二個(gè)版本接受 first 和 last 兩個(gè)迭代器,將[first,last)范圍內(nèi)的所有對象析構(gòu)掉。

其中 destroy() 只截取了部分源碼,全部實(shí)現(xiàn)還考慮到特化版本,比如判斷元素的數(shù)值類型 (value type) 是否有 trivial destructor 等限于篇幅,完整代碼請參閱《STL 源碼剖析》。

再來張圖吧,獻(xiàn)丑了。

5. 內(nèi)存的配置與釋放

前面所講都是對象的構(gòu)造和析構(gòu),接下來要講的是對象構(gòu)造和析構(gòu)背后的故事—(內(nèi)存的分配與釋放),這塊是才真正的硬核,不要搞混了哦。

5.1 真· alloc 設(shè)計(jì)奧義

對象構(gòu)造和析構(gòu)之后的內(nèi)存管理諸項(xiàng)事宜,由 一律負(fù)責(zé)。SGI 對此的設(shè)計(jì)原則如下:

a. 向 system heap 要求空間

b. 考慮多線程 (multi-threads) 狀態(tài)

c. 考慮內(nèi)存不足時(shí)的應(yīng)變措施

d. 考慮過多“小型區(qū)塊”可能造成的內(nèi)存碎片 (fragment) 問題

考慮到小型區(qū)塊可能造成的內(nèi)存破碎問題,SGI 為此設(shè)計(jì)了雙層級配置器。當(dāng)配置區(qū)塊超過 128bytes 時(shí),稱為足夠大,使用第一級配置器,直接使用 malloc() 和 free()。

當(dāng)配置區(qū)塊不大于 128bytes 時(shí),為了降低額外負(fù)擔(dān),直接使用第二級配置器,采用復(fù)雜的 memory pool 處理方式。

無論使用第一級配接器(__malloc_alloc_template)或是第二級配接器(__default_alloc_template),alloc 都為其包裝了接口,使其能夠符合 STL 標(biāo)準(zhǔn)。

其中, __malloc_alloc_template 就是第一級配置器;

__default_alloc_template 就是第二級配置器。

這么一大堆源碼看懵了吧,別著急,請看下圖。

其中 SGI STL 將配置器多了一層包裝使得 Alloc 具備標(biāo)準(zhǔn)接口。

6.?alloc 一級配置器源碼解讀

這里截取部分(精華)解讀

(1)第一級配置器以 malloc(), free(), realloc() 等 C 函數(shù)執(zhí)行實(shí)際的內(nèi)存配置、釋放和重配置操作,并實(shí)現(xiàn)類似 C++ new-handler 的機(jī)制(因?yàn)樗⒎鞘褂?::operator new 來配置內(nèi)存,所以不能直接使用C++ new-handler 機(jī)制)。

(2)SGI 第一級配置器的 allocate() 和 reallocate() 都是在調(diào)用malloc() 和 realloc() 不成功后,改調(diào)用 oom_malloc() 和oom_realloc()。

(3)oom_malloc()?和 oom_realloc() 都有內(nèi)循環(huán),不斷調(diào)用“內(nèi)存不足處理例程”,期望某次調(diào)用后,獲得足夠的內(nèi)存而圓滿完成任務(wù),哪怕有一絲希望也要全力以赴申請啊,如果用戶并沒有指定“內(nèi)存不足處理程序”,這個(gè)時(shí)候便無力乏天,真的是沒內(nèi)存了,STL 便拋出異常?;蛘{(diào)用exit(1) 終止程序。

7.?alloc 二級配置器源碼解讀

照例,還是截取部分(精華)源碼解讀。看累了嘛,遠(yuǎn)眺歇會(huì),回來繼續(xù)看,接下來的這部分,將會(huì)更加的讓我們?yōu)榇髱煹闹腔壅鄯?/span>

第二級配置器多了一些機(jī)制,專門針對內(nèi)存碎片。內(nèi)存碎片化帶來的不僅僅是回收時(shí)的困難,配置也是一個(gè)負(fù)擔(dān),額外負(fù)擔(dān)永遠(yuǎn)無法避免,畢竟系統(tǒng)要?jiǎng)澇鲞@么多的資源來管理另外的資源,但是區(qū)塊越小,額外負(fù)擔(dān)率就越高。

7.1 SGI 第二級配置器到底解決了多少問題呢?

簡單來說 SGI第二級配置器的做法是:sub-allocation (層次架構(gòu)):

前面也說過了,SGI STL 的第一級配置器是直接使用 malloc(), free(), realloc() 并配合類似 C++ new-handler 機(jī)制實(shí)現(xiàn)的。第二級配置器的工作機(jī)制要根據(jù)區(qū)塊的大小是否大于 128bytes 來采取不同的策略:

繼續(xù)跟上節(jié)奏,上面提到了 memory pool ,相信程序員朋友們很熟悉這個(gè)名詞了,沒錯(cuò),這就是二級配置器的精髓所在,如何理解?請看下圖:

有了內(nèi)存池,是不是就可以了,當(dāng)然沒有這么簡單。上圖中還提到了自由鏈表,這個(gè)又是何方神圣?

我們來一探究竟!

7.2 自由鏈表自由在哪?又該如何維護(hù)呢?

我們知道,一方面,自由鏈表中有些區(qū)塊已經(jīng)分配給了客端使用,所以 free_list 不需要再指向它們;另一方面,為了維護(hù) free-list,每個(gè)節(jié)點(diǎn)還需要額外的指針指向下一個(gè)節(jié)點(diǎn)。

那么問題來了,如果每個(gè)節(jié)點(diǎn)有兩個(gè)指針?這不就造成了額外負(fù)擔(dān)嗎?本來我們設(shè)計(jì) STL 容器就是用來保存對象的,這倒好,對象還沒保存之前,已經(jīng)占據(jù)了額外的內(nèi)存空間了。那么,有方法解決嗎?當(dāng)然有!再來感受一次大師的智慧!

(1)在這之前我們先來了解另一個(gè)概念——union(聯(lián)合體/共用體),對 union 已經(jīng)熟悉的讀者可以跳過這一部分的內(nèi)容;如果忘記了也沒關(guān)系,趁此來回顧一下:

(a)共用體是一種特殊的類,也是一種構(gòu)造類型的數(shù)據(jù)結(jié)構(gòu)。

(b)共用體表示幾個(gè)變量共用一個(gè)內(nèi)存位置,在不同的時(shí)間保存不同的數(shù)據(jù)類型和不同長度的變量。

(c)所有的共用體成員共用一個(gè)空間,并且同一時(shí)間只能儲(chǔ)存其中一個(gè)成員變量的值。例如如下:

一個(gè)union 只配置一個(gè)足夠大的空間以來容納最大長度的數(shù)據(jù)成員,以上例而言,最大長度是 double 類型,

所以 ChannelManager 的空間大小就是 double 數(shù)據(jù)類型的大小。在 C++ 里,union 的成員默認(rèn)屬性頁為 public。union 主要用來壓縮空間,如果一些數(shù)據(jù)不可能在同一時(shí)間同時(shí)被用到,則可以使用 union。

(2)了解了 union 之后,我們可以借助 union 的幫助,先來觀察一下 free-list 節(jié)點(diǎn)的結(jié)構(gòu)

來深入了解 free_list 的實(shí)現(xiàn)技巧,請看下圖。

在 union obj 中,定義了兩個(gè)字段,再結(jié)合上圖來分析:

從第一個(gè)字段看,obj 可以看做一個(gè)指針,指向鏈表中的下一個(gè)節(jié)點(diǎn);

從第二個(gè)字段看,obj 可以也看做一個(gè)指針,不過此時(shí)是指向?qū)嶋H的內(nèi)存區(qū)。

一物二用的好處就是不會(huì)為了維護(hù)鏈表所必須的指針而造成內(nèi)存的另一種浪費(fèi),或許這就是所謂的自由奧義所在!大師的智慧躍然紙上。

7.3 第二級配置器的部分實(shí)現(xiàn)內(nèi)容

到這里,我們已經(jīng)基本了解了第二級配置器中 free_list 的工作原理了。附上第二級配置器的部分實(shí)現(xiàn)內(nèi)容源碼:

太長了吧,看懵逼了,沒關(guān)系,請耐心接著往下看。

8. 空間配置器函數(shù)allocate源碼解讀

我們知道第二級配置器擁有配置器的標(biāo)準(zhǔn)接口函數(shù) allocate()。此函數(shù)首先判斷區(qū)塊的大小,如果大于 128bytes –> 調(diào)用第一級配置器;小于128bytes–> 就檢查對應(yīng)的 free_list(如果沒有可用區(qū)塊,就將區(qū)塊上調(diào)至 8 倍數(shù)的邊界,然后調(diào)用 refill(), 為 free list 重新填充空間。

8.1 空間申請

調(diào)用標(biāo)準(zhǔn)接口函數(shù) allocate():

NOTE:每次都是從對應(yīng)的 free_list 的頭部取出可用的內(nèi)存塊。然后對free_list 進(jìn)行調(diào)整,使上一步撥出的內(nèi)存的下一個(gè)節(jié)點(diǎn)變?yōu)轭^結(jié)點(diǎn)。

8.2?空間釋放

同樣,作為第二級配置器擁有配置器標(biāo)準(zhǔn)接口函數(shù) deallocate()。該函數(shù)首先判斷區(qū)塊大小,大于 128bytes 就調(diào)用第一級配置器。小于 128bytes 就找出對應(yīng)的 free_list,將區(qū)塊回收。

NOTE:通過調(diào)整free_ list鏈表將區(qū)塊放入free list的頭部。

區(qū)塊回收納入 free_list 的操作,如圖所示:

8.3 重新填充 free_lists

(1)當(dāng)發(fā)現(xiàn) free list 中沒有可用區(qū)塊時(shí),就會(huì)調(diào)用 refill() 為free_list 重新填充空間;

(2)新的空間將取自內(nèi)存池(經(jīng)由 chunk_alloc() 完成);

(3)缺省取得20個(gè)新節(jié)點(diǎn)(區(qū)塊),但萬一內(nèi)存池空間不足,獲得的節(jié)點(diǎn)數(shù)可能小于 20。

8.4 內(nèi)存池(memory pool)

唔…在前面提到了 memory pool,現(xiàn)在終于輪到這個(gè)大 boss 上場。

首先,我們要知道從內(nèi)存池中取空間給 free_list 使用,是 chunk_alloc() 在工作,它是怎么工作的呢?

我們先來分析 chunk_alloc() 的工作機(jī)制:

chunk_alloc() 函數(shù)以 end_free – start_free 來判斷內(nèi)存池的“水量”(哈哈,很形象的比喻)。

具體邏輯都在下面的圖里了,很形象吧。

如果第一級配置器的 malloc()?也失敗了,就發(fā)出 bad_alloc 異常。

說了這么多來看一下 STL 的源碼吧。

太長了,又看懵逼了吧,沒關(guān)系,請看下圖。

NOTE:上述就是 STL 源碼當(dāng)中實(shí)際內(nèi)存池的操作原理,我們可以看到其實(shí)以共用體串聯(lián)起來共享內(nèi)存形成了free_list的實(shí)質(zhì)組成。

9.本文小結(jié)

STL 源碼本身博大精深,還有很多精妙的設(shè)計(jì)等著大家去探索。

小賀本人才疏學(xué)淺,在這里也只是在自己掌握的程度下寫出自己的理解,不足之處希望對大家多多指出,互相討論學(xué)習(xí)。

這篇肝了一個(gè)禮拜的文章,在周日的晚上終于寫完了,文中所有的圖都是自己一個(gè)個(gè)親手畫的,不畫不知道,畫完之后真心感覺不容易啊。

第一次寫這種圖解的文章,不知道讀者朋友們喜不喜歡。如果反饋還不錯(cuò)的話,后面有時(shí)間會(huì)繼續(xù)更新類型的圖解文章。

參考文章:

《STL源碼剖析-侯捷》

https://cloud.tencent.com/developer/article/1686585

https://dulishu.top/allocator-of-stl/

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉