chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C++內(nèi)存管理全景指南

Linux愛(ài)好者 ? 來(lái)源:Linux愛(ài)好者 ? 作者:Linux愛(ài)好者 ? 2021-03-03 15:05 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

隨著人工智能,云計(jì)算等技術(shù)的迅猛發(fā)展,讓Python,go等新興語(yǔ)言流行了起來(lái),很多人以為C++可能已經(jīng)過(guò)時(shí)了,確實(shí),C++編程語(yǔ)言走到今天已經(jīng)有將近40年的歷史了,但它依然是當(dāng)今的主流語(yǔ)言,我們可以看一下世界權(quán)威編程語(yǔ)言排行榜,C++依然是屬于第一梯隊(duì),C++在金融交易系統(tǒng),游戲,數(shù)據(jù)庫(kù),編譯器,大型桌面程序,高性能服務(wù)器,瀏覽器,各類(lèi)編程比賽(ACM-ICPC,Topcoder,Codeforces,Google Code Jam)等領(lǐng)域任然是主力軍。

fa3d4850-7243-11eb-8b86-12bb97331649.png

在各個(gè)大廠情況,C++也是很多大廠主力編程語(yǔ)言,國(guó)外google和微軟大部分核心產(chǎn)品都是基于C++開(kāi)發(fā)的;鵝廠編程語(yǔ)言TOP5,C++排第一:

C++的高抽象層次,又兼具高性能,是其他語(yǔ)言所無(wú)法替代的,C++標(biāo)準(zhǔn)保持穩(wěn)定發(fā)展,更加現(xiàn)代化,更加強(qiáng)大,更加易用,熟練的 C++ 工程師自然也獲得了“高水平、高薪資”的名聲,但在各種活躍編程語(yǔ)言中,C++門(mén)檻依然很高,尤其C++的內(nèi)存問(wèn)題(內(nèi)存泄露,內(nèi)存溢出,內(nèi)存宕機(jī),堆棧破壞等問(wèn)題),需要理解C++標(biāo)準(zhǔn)對(duì)象模型,C++標(biāo)準(zhǔn)庫(kù),標(biāo)準(zhǔn)C庫(kù),操作系統(tǒng)等內(nèi)存設(shè)計(jì),才能更加深入理解C++內(nèi)存管理,這是跨越C++三座大山之一,我們必須拿下它。

Content

fb058bbc-7243-11eb-8b86-12bb97331649.png

環(huán)境:

uname -a Linux alexfeng 3.19.0-15-generic #15-Ubuntu SMP Thu Apr 16 2337 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux cat /proc/cpuinfo bugs : bogomips : 4800.52 clflush size : 64 cache_alignment : 64 address sizes : 36 bits physical, 48 bits virtual cat /proc/meminfo MemTotal: 4041548 kB(4G) MemFree: 216304 kB MemAvailable: 2870340 kB Buffers: 983360 kB Cached: 1184008 kB SwapCached: 54528 kB GNU gdb (Ubuntu 7.9-1ubuntu1) 7.9 g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2

一 C++內(nèi)存模型

C++11在標(biāo)準(zhǔn)庫(kù)中引入了memory model,這應(yīng)該是C++11最重要的特性之一了。C++11引入memory model的意義在于我們可以在high level language層面實(shí)現(xiàn)對(duì)在多處理器中多線程共享內(nèi)存交互的控制。我們可以在語(yǔ)言層面忽略compiler,CPU arch的不同對(duì)多線程編程的影響了。我們的多線程可以跨平臺(tái)。

內(nèi)存模型

為 C++ 定義計(jì)算機(jī)內(nèi)存存儲(chǔ)的語(yǔ)義。可用于 C++ 程序的內(nèi)存是一或多個(gè)相接的字節(jié)序列。內(nèi)存中的每個(gè)字節(jié)擁有唯一的地址。

字節(jié)

字節(jié)是最小的可尋址內(nèi)存單元。它被定義為相接的位序列,大到足以保有任何UTF-8編碼單元( 256 個(gè)相異值)和(C++14 起)基本執(zhí)行字符集(要求為單字節(jié)的 96 個(gè)字符)的任何成員。類(lèi)似 C , C++ 支持 8 位或更大的字節(jié)。char 、 unsigned char 和 signed char 類(lèi)型把一個(gè)字節(jié)用于存儲(chǔ)和值表示。

字節(jié)中的位數(shù)可作為 CHAR_BIT 或 std::numeric_limits::digits 訪問(wèn)。

內(nèi)存位置

內(nèi)存位置是

一個(gè)標(biāo)量類(lèi)型(算術(shù)類(lèi)型、指針類(lèi)型、枚舉類(lèi)型或 std::nullptr_t )對(duì)象

或非零長(zhǎng)位域的最大相接序列

注意:各種語(yǔ)言特性,例如引用和虛函數(shù),可能涉及到程序不可訪問(wèn),但為實(shí)現(xiàn)所管理的額外內(nèi)存位置。

線程與數(shù)據(jù)競(jìng)爭(zhēng)

執(zhí)行線程是程序中的控制流,它始于 std::thread 、 std::async 或以其他方式所做的頂層函數(shù)調(diào)用。

任何線程都能潛在地訪問(wèn)程序中的任何對(duì)象(擁有自動(dòng)或線程局域存儲(chǔ)期的對(duì)象仍可為另一線程通過(guò)指針或引用訪問(wèn))。

始終允許不同的執(zhí)行線程同時(shí)訪問(wèn)(讀和寫(xiě))不同的內(nèi)存位置,而無(wú)沖突或同步要求。

一個(gè)表達(dá)式的求值寫(xiě)入內(nèi)存位置,而另一求值讀或?qū)懲粌?nèi)存位置時(shí),稱(chēng)這些表達(dá)式?jīng)_突。擁有二個(gè)沖突求值的程序有數(shù)據(jù)競(jìng)爭(zhēng),除非

兩個(gè)求值都在同一線程上,或同一信號(hào)處理函數(shù)中執(zhí)行,或

兩個(gè)沖突求值都是原子操作(見(jiàn)std::atomic),或

一個(gè)沖突求值先發(fā)生于( happens-before )另一個(gè)(見(jiàn)內(nèi)存順序--std::memory_order)

若出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng),則程序的行為未定義。

內(nèi)存順序(std::memory_order)

如果不使用任何同步機(jī)制(例如 mutex 或 atomic),在多線程中讀寫(xiě)同一個(gè)變量,那么程序的結(jié)果是難以預(yù)料的。簡(jiǎn)單來(lái)說(shuō),編譯器以及 CPU 的一些行為,會(huì)影響到C++程序的執(zhí)行結(jié)果

即使是簡(jiǎn)單的語(yǔ)句,C++ 也不保證是原子操作。

CPU 可能會(huì)調(diào)整指令的執(zhí)行順序。

在 CPU cache 的影響下,一個(gè) CPU 執(zhí)行了某個(gè)指令,不會(huì)立即被其它 CPU 看見(jiàn)。

Intel x86, x86-64等屬于強(qiáng)排序CPU,x86-64的強(qiáng)內(nèi)存模型總能保證按順序執(zhí)行,遵從數(shù)據(jù)依賴(lài)順序,但PowerPC和ARM是弱排序CPU,有時(shí)需要依賴(lài)內(nèi)存柵欄指令。

多線程讀寫(xiě)同一變量需要使用同步機(jī)制,最常見(jiàn)的同步機(jī)制就是std::mutex和std::atomic。然而從性能角度看,通常使用std::atomic會(huì)獲得更好的性能.

C++11 提供6 種可以應(yīng)用于原子變量的內(nèi)存次序:

momory_order_relaxed,

memory_order_consume,

memory_order_acquire,

memory_order_release,

memory_order_acq_rel,

memory_order_seq_cst

雖然共有 6 個(gè)選項(xiàng),但它們表示的是四種內(nèi)存模型:

Relaxed ordering

Release-Acquire ordering

Release-Consume ordering

Sequentially-consistent ordering

順序一致次序(sequential consisten ordering)

對(duì)應(yīng)memory_order_seq_cst. SC作為默認(rèn)的內(nèi)存序,是因?yàn)樗馕吨鴮⒊绦蚩醋鍪且粋€(gè)簡(jiǎn)單的序列。如果對(duì)于一個(gè)原子變量的操作都是順序一致的,那么多線程程序的行為就像是這些操作都以一種特定順序被單線程程序執(zhí)行。從同的角度來(lái)看,一個(gè)順序一致的 store 操作 synchroniezd-with 一個(gè)順序一致的需要讀取相同的變量的 load 操作。除此以外,順序模型還保證了在 load 之后執(zhí)行的順序一致原子操作都得表現(xiàn)得在 store 之后完成。非順序一致內(nèi)存次序(non-sequentially consistency memory ordering)強(qiáng)調(diào)對(duì)同一事件(代碼),不同線程可以以不同順序去執(zhí)行,不僅是因?yàn)榫幾g器可以進(jìn)行指令重排,也因?yàn)椴煌?CPU cache 及內(nèi)部緩存的狀態(tài)可以影響這些指令的執(zhí)行。但所有線程仍需要對(duì)某個(gè)變量的連續(xù)修改達(dá)成順序一致。

松弛次序(relaxed ordering)

在這種模型下,std::atomic的load()和store()都要帶上memory_order_relaxed參數(shù)。Relaxed ordering 僅僅保證load()和store()是原子操作,除此之外,不提供任何跨線程的同步。

獲取-釋放次序(acquire-release ordering)

在這種模型下,store()使用memory_order_release,而load()使用memory_order_acquire。這種模型有兩種效果,第一種是可以限制 CPU 指令的重排:

在store()之前的所有讀寫(xiě)操作,不允許被移動(dòng)到這個(gè)store()的后面。

在load()之后的所有讀寫(xiě)操作,不允許被移動(dòng)到這個(gè)load()的前面。

數(shù)據(jù)依賴(lài)(Release-Consume ordering)

memory_order_consume 是 acquire-release 順序模型中的一種,但它比較特殊,它為 inter-thread happens-before 引入了數(shù)據(jù)依賴(lài)關(guān)系:dependency-ordered-before ,一個(gè)使用memory_order_consume的操作具有消費(fèi)語(yǔ)義(consume semantics)。我們稱(chēng)這個(gè)操作為消費(fèi)操作(consume operations),對(duì)于memory_order_consume最的價(jià)值的觀察結(jié)果就是總是可以安全的將它替換成memory_order_acquire,消費(fèi)和獲取都為了同一個(gè)目的:幫助非原子信息在線程間安全的傳遞。就像獲取操作一樣,消費(fèi)操作必須與另一個(gè)線程的釋放操作一起使用。它們之間主要的區(qū)別在于消費(fèi)操作可以正確起作用的案例更少。相對(duì)于它的使用不便,反過(guò)來(lái)也就意味著消費(fèi)操作在某些平臺(tái)使用更有效。

默認(rèn)情況下,std::atomic使用的是 Sequentially-consistent ordering。但在某些場(chǎng)景下,合理使用其它三種 ordering,可以讓編譯器優(yōu)化生成的代碼,從而提高性能。

思考問(wèn)題:

1 C++正常程序可以訪問(wèn)到哪些內(nèi)存和不能訪問(wèn)到哪些內(nèi)存(這些內(nèi)存屬于該程序)?

2 內(nèi)存對(duì)程序并發(fā)執(zhí)行有什么影響?

3 std::memory_order 的作用是什么?

二 C++對(duì)象內(nèi)存模型

1 空類(lèi)對(duì)象(一般作為模板的tag來(lái)使用)

classA{};sizeof(A)=1C++標(biāo)準(zhǔn)要求C++的對(duì)象大小不能為0,C++對(duì)象必須在內(nèi)存里面有唯一的地址,但又不想浪費(fèi)太多內(nèi)存空間,所以標(biāo)準(zhǔn)規(guī)定為1byte,

fb82e1e8-7243-11eb-8b86-12bb97331649.png

2非空類(lèi)

classA { public: inta; }; sizeof(A)=8,align=8

fbed5550-7243-11eb-8b86-12bb97331649.png

3 非空虛基類(lèi)classA { public: inta; virtualvoidv(); }; sizeof(A)=16,align=8

fc4bfd62-7243-11eb-8b86-12bb97331649.png

4 單繼承

classA{ public: inta; virtualvoidv(); }; classB:publicA{ public: intb; }; sizeof(B)=16,align=8

fc7e0ece-7243-11eb-8b86-12bb97331649.png

5 簡(jiǎn)單多繼承

classA{ public: inta; virtualvoidv(); }; classB{ public: intb; virtualvoidw(); }; classC:publicA,publicB{ public: intc; }; sizeof(C)=32,align=8

fcac7e9e-7243-11eb-8b86-12bb97331649.png

6 簡(jiǎn)單多繼承-2

classA{ public: inta; virtualvoidv(); }; classB{ public: intb; virtualvoidw(); }; classC:publicA,publicB{ public: intc; voidw(); }; sizeof(C)=32,align=8

fce64de0-7243-11eb-8b86-12bb97331649.png

7The Diamond:多重繼承 (沒(méi)有虛繼承)classA{ public: inta; virtualvoidv(); }; classB:publicA{ public: intb; virtualvoidw(); }; classC:publicA{ public: intc; virtualvoidx(); }; classD:publicB,publicC{ public: intd; virtualvoidy(); }; sizeof(D)=40align=8

fd31830a-7243-11eb-8b86-12bb97331649.png

注意點(diǎn):此種繼承存在兩份基類(lèi)成員,使用時(shí)候需要指定路徑,不方便,易出錯(cuò)。

8The Diamond: 鉆石類(lèi)虛繼承

解決上面的問(wèn)題,讓基類(lèi)只有存在一份,共享基類(lèi);

classA{ public: inta; virtualvoidv(); }; classB:publicvirtualA{ public: intb; virtualvoidw(); }; classC:publicvirtualA{ public: intc; virtualvoidx(); }; classD:publicB,publicC{ public: intd; virtualvoidy(); }; sizeof(D)=48,align=8

fd74d448-7243-11eb-8b86-12bb97331649.png

注意點(diǎn):1.top_offset表示this指針對(duì)子類(lèi)的偏移,用于子類(lèi)和繼承類(lèi)之間dynamic_cast轉(zhuǎn)換(還需要typeinfo數(shù)據(jù)),實(shí)現(xiàn)多態(tài),vbase_offset 表示this指針對(duì)基類(lèi)的偏移,用于共享基類(lèi);2.gcc為了每一個(gè)類(lèi)生成一個(gè)vtable虛函數(shù)表,放在程序的.rodata段,其他編譯器(平臺(tái))比如vs,實(shí)現(xiàn)不太一樣.3.gcc還有VTT表,里面存放了各個(gè)基類(lèi)之間虛函數(shù)表的關(guān)系,最大化利用基類(lèi)的虛函數(shù)表,專(zhuān)門(mén)用來(lái)為構(gòu)建最終類(lèi)vtable;4.在構(gòu)造函數(shù)里面設(shè)置對(duì)象的vtptr指針。5.虛函數(shù)表地址的前面設(shè)置了一個(gè)指向type_info的指針,RTTI(Run Time Type Identification)運(yùn)行時(shí)類(lèi)型識(shí)別是有編譯器在編譯器生成的特殊類(lèi)型信息,包括對(duì)象繼承關(guān)系,對(duì)象本身的描述,RTTI是為多態(tài)而生成的信息,所以只有具有虛函數(shù)的對(duì)象在會(huì)生成。6.在C++類(lèi)中有兩種成員數(shù)據(jù):static、nonstatic;三種成員函數(shù):static、nonstatic、virtual。

C++成員非靜態(tài)數(shù)據(jù)需要占用動(dòng)態(tài)內(nèi)存,?;蛘叨阎?,其他static數(shù)據(jù)存在全局變量區(qū)(數(shù)據(jù)段),編譯時(shí)候確定。虛函數(shù)會(huì)增加用虛函數(shù)表大小,也是存儲(chǔ)在數(shù)據(jù)區(qū)的.rodada段,編譯時(shí)確定,其他函數(shù)不占空間。

7.G++選項(xiàng)-fdump-class-hierarchy 可以生成C++類(lèi)層結(jié)構(gòu),虛函數(shù)表結(jié)構(gòu),VTT表結(jié)構(gòu)。

8.GDB調(diào)試選項(xiàng):

set p obj :在C++中,如果一個(gè)對(duì)象指針指向其派生類(lèi), 如果打開(kāi)這個(gè)選項(xiàng),GDB會(huì)現(xiàn)在類(lèi)對(duì)象結(jié)構(gòu)的規(guī)則顯示輸出。

set p pertty : 按照層次打印結(jié)構(gòu)體。

思考問(wèn)題:

1Why don't we havevirtualconstructors?

From Bjarne Stroustrup's C++ Style and Technique FAQ

A virtual call is a mechanism to get work done given partial information. In particular, "virtual" allows us to call a function knowing only any interfaces and not the exact type of the object. To create an object you need complete information. In particular, you need to know the exact type of what you want to create. Consequently, a "call to a constructor" cannot be virtual.

2 為什么不要在構(gòu)造函數(shù)或者析構(gòu)函數(shù)中調(diào)用虛函數(shù)?

對(duì)于構(gòu)造函數(shù):此時(shí)子類(lèi)的對(duì)象還沒(méi)有完全構(gòu)造,編譯器會(huì)去虛函數(shù)化,只會(huì)用當(dāng)前類(lèi)的函數(shù), 如果是純虛函數(shù),就會(huì)調(diào)用到純虛函數(shù),會(huì)導(dǎo)致構(gòu)造函數(shù)拋異常:pure virtual method calle;對(duì)于析構(gòu)函數(shù):同樣,由于對(duì)象不完整,編譯器會(huì)去虛函數(shù)化,函數(shù)調(diào)用本類(lèi)的虛函數(shù),如果本類(lèi)虛函數(shù)是純虛函數(shù),就會(huì)到賬析構(gòu)函數(shù)拋出異常: pure virtual method called;

3 C++對(duì)象構(gòu)造順序?

1.構(gòu)造子類(lèi)構(gòu)造函數(shù)的參數(shù)

2.子類(lèi)調(diào)用基類(lèi)構(gòu)造函數(shù)

3.基類(lèi)設(shè)置vptr

4.基類(lèi)初始化列表內(nèi)容進(jìn)行構(gòu)造

5. 基類(lèi)函數(shù)體調(diào)用

6. 子類(lèi)設(shè)置vptr

7. 子類(lèi)初始化列表內(nèi)容進(jìn)行構(gòu)造

8. 子類(lèi)構(gòu)造函數(shù)體調(diào)用

4 為什么虛函數(shù)會(huì)降低效率?

是因?yàn)樘摵瘮?shù)調(diào)用執(zhí)行過(guò)程中會(huì)跳轉(zhuǎn)兩次,首先找到虛函數(shù)表,然后再查找對(duì)應(yīng)函數(shù)地址,這樣CPU指令就會(huì)跳轉(zhuǎn)兩次,而普通函數(shù)指跳轉(zhuǎn)一次,CPU每跳轉(zhuǎn)一次,預(yù)取指令都可能作廢,這會(huì)導(dǎo)致分支預(yù)測(cè)失敗,流水線排空,所以效率會(huì)變低。設(shè)想一下,如果說(shuō)不是虛函數(shù),那么在編譯時(shí)期,其相對(duì)地址是確定的,編譯器可以直接生成jmp/invoke指令;如果是虛函數(shù),多出來(lái)的一次查找vtable所帶來(lái)的開(kāi)銷(xiāo),倒是次要的,關(guān)鍵在于,這個(gè)函數(shù)地址是動(dòng)態(tài)的,譬如 取到的地址在eax里,則在call eax之后的那些已經(jīng)被預(yù)取進(jìn)入流水線的所有指令都將失效。流水線越長(zhǎng),一次分支預(yù)測(cè)失敗的代價(jià)也就越大。

三 C++程序運(yùn)行內(nèi)存空間模型

1. C++程序大致運(yùn)行內(nèi)存空間:

32位:

fdacc20e-7243-11eb-8b86-12bb97331649.png

64位:

fddd3a42-7243-11eb-8b86-12bb97331649.png

2 Linux虛擬內(nèi)存內(nèi)部實(shí)現(xiàn)

fe205c8c-7243-11eb-8b86-12bb97331649.png

關(guān)鍵點(diǎn):

1 各個(gè)分區(qū)的意義

內(nèi)核空間:在32位系統(tǒng)中,Linux會(huì)留1G空間給內(nèi)核,用戶(hù)進(jìn)程是無(wú)法訪問(wèn)的,用來(lái)存放進(jìn)程相關(guān)數(shù)據(jù)和內(nèi)存數(shù)據(jù),內(nèi)核代碼等;在64位系統(tǒng)里面,Linux會(huì)采用最低48位來(lái)表示虛擬內(nèi)存,這可通過(guò) /proc/cpuinfo 來(lái)查看address sizes :

address sizes : 36 bits physical, 48 bits virtual,總的虛擬地址空間為256TB( 2^48 ),在這256TB的虛擬內(nèi)存空間中, 0000000000000000 - 00007fffffffffff(128TB)為用戶(hù)空間,ffff800000000000 - ffffffffffffffff(128TB)為內(nèi)核空間。目前常用的分配設(shè)計(jì):

Virtualmemorymapwith4levelpagetables: 0000000000000000-00007fffffffffff(=47bits)userspace,differentpermm holecausedby[47:63]signextension ffff800000000000-ffff87ffffffffff(=43bits)guardhole,reservedforhypervisor ffff880000000000-ffffc7ffffffffff(=64TB)directmappingofallphys.memory ffffc80000000000-ffffc8ffffffffff(=40bits)hole ffffc90000000000-ffffe8ffffffffff(=45bits)vmalloc/ioremapspace ffffe90000000000-ffffe9ffffffffff(=40bits)hole ffffea0000000000-ffffeaffffffffff(=40bits)virtualmemorymap(1TB) ...unusedhole... ffffec0000000000-fffffbffffffffff(=44bits)kasanshadowmemory(16TB) ...unusedhole... vaddr_endforKASLR fffffe0000000000-fffffe7fffffffff(=39bits)cpu_entry_areamapping fffffe8000000000-fffffeffffffffff(=39bits)LDTremapforPTI ffffff0000000000-ffffff7fffffffff(=39bits)%espfixupstacks ...unusedhole... ffffffef00000000-fffffffeffffffff(=64GB)EFIregionmappingspace ...unusedhole... ffffffff80000000-ffffffff9fffffff(=512MB)kerneltextmapping,fromphys0 ffffffffa0000000-fffffffffeffffff(1520MB)modulemappingspace [fixmapstart]-ffffffffff5fffffkernel-internalfixmaprange ffffffffff600000-ffffffffff600fff(=4kB)legacyvsyscallABI ffffffffffe00000-ffffffffffffffff(=2MB)unusedholehttp://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt

剩下的是用戶(hù)內(nèi)存空間:

stack棧區(qū):專(zhuān)門(mén)用來(lái)實(shí)現(xiàn)函數(shù)調(diào)用-棧結(jié)構(gòu)的內(nèi)存塊。相對(duì)空間下(可以設(shè)置大小,Linux 一般默認(rèn)是8M,可通過(guò) ulimit –s 查看),系統(tǒng)自動(dòng)管理,從高地址往低地址,向下生長(zhǎng)。

內(nèi)存映射區(qū):包括文件映射和匿名內(nèi)存映射, 應(yīng)用程序的所依賴(lài)的動(dòng)態(tài)庫(kù),會(huì)在程序執(zhí)行時(shí)候,加載到內(nèi)存這個(gè)區(qū)域,一般包括數(shù)據(jù)(data)和代碼(text);通過(guò)mmap系統(tǒng)調(diào)用,可以把特定的文件映射到內(nèi)存中,然后在相應(yīng)的內(nèi)存區(qū)域中操作字節(jié)來(lái)訪問(wèn)文件內(nèi)容,實(shí)現(xiàn)更高效的IO操作;匿名映射,在glibc中malloc分配大內(nèi)存的時(shí)候會(huì)用到匿名映射。這里所謂的“大”表示是超過(guò)了MMAP_THRESHOLD設(shè)置的字節(jié)數(shù),它的缺省值是 128 kB,可以通過(guò)mallopt()去調(diào)整這個(gè)設(shè)置值。還可以用于進(jìn)程間通信IPC(共享內(nèi)存)。

heap堆區(qū):主要用于用戶(hù)動(dòng)態(tài)內(nèi)存分配,空間大,使用靈活,但需要用戶(hù)自己管理,通過(guò)brk系統(tǒng)調(diào)用控制堆的生長(zhǎng),向高地址生長(zhǎng)。

BBS段和DATA段:用于存放程序全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù),一般未初始化的放在BSS段(統(tǒng)一初始化為0,不占程序文件的空間),初始化的放在data段,只讀數(shù)據(jù)放在rodata段(常量存儲(chǔ)區(qū))。

text段:主要存放程序二進(jìn)制代碼。

2 為了防止內(nèi)存被攻擊,比如棧溢出攻擊和堆溢出攻擊等,Linux在特定段之間使用隨機(jī)偏移,使段的起始地址是隨機(jī)值, Linux 系統(tǒng)上的ASLR 等級(jí)可以通過(guò)文件 /proc/sys/kernel/randomize_va_space 來(lái)進(jìn)行設(shè)置,它支持以下取值:

0 - 關(guān)閉的隨機(jī)化。一切都是靜止的。

1 - 保守的隨機(jī)化。共享庫(kù)、棧、mmap()、VDSO以及堆將被隨機(jī)化。

2 - 完全的隨機(jī)化。除了上面列舉的要素外,通過(guò) brk() 分配得到的內(nèi)存空間也將被隨機(jī)化。

3 每個(gè)段都有特定的安全控制(權(quán)限):

vm_flags 第三列,如r-xp 此段虛擬地址空間的屬性。每種屬性用一個(gè)字段表示,r表示可讀,w表示可寫(xiě),x表示可執(zhí)行,p和s共用一個(gè)字段,互斥關(guān)系,p表示私有段,s表示共享段,如果沒(méi)有相應(yīng)權(quán)限,則用’-’代替

4 Linux虛擬內(nèi)存是按頁(yè)分配,每頁(yè)大小為4KB或者2M,1G等(大頁(yè)內(nèi)存), 默認(rèn)是4K;

5 例子-通過(guò)pmap 查看程序內(nèi)存布局(綜合proc/x/maps與proc/x/smaps數(shù)據(jù)):

#include #include usingnamespacestd; //longa[1024*1024]={0}; intmain() { void*heap; int*x=newint[1024](); cout<

關(guān)閉內(nèi)存地址隨機(jī)化

pmap-X8117 8117:./main AddressPermOffsetDeviceInodeSizeRssPssReferencedAnonymousSwapLockedMapping 00400000r-xp0000000008:11430142354444000main 00601000r--p0000100008:11430142354444400main 00602000rw-p0000200008:11430142354444400main //程序的text段,只讀數(shù)據(jù)段,和全局/靜態(tài)數(shù)據(jù)段; 00603000rw-p0000000000:000136888800[heap] //程序的堆內(nèi)存段; 7ffff71e2000r-xp0000000008:1126640188881888000libgcc_s.so.1 7ffff71f8000---p0001600008:112664012044000000libgcc_s.so.1 7ffff73f7000rw-p0001500008:112664014444400libgcc_s.so.1 7ffff73f8000r-xp0000000008:1126643110522243224000libm-2.21.so 7ffff74ff000---p0010700008:112664312044000000libm-2.21.so 7ffff76fe000r--p0010600008:112664314444400libm-2.21.so 7ffff76ff000rw-p0010700008:112664314444400libm-2.21.so 7ffff7700000r-xp0000000008:112663721792115281152000libc-2.21.so 7ffff78c0000---p001c000008:112663722048000000libc-2.21.so 7ffff7ac0000r--p001c000008:11266372161616161600libc-2.21.so 7ffff7ac4000rw-p001c400008:112663728888800libc-2.21.so 7ffff7ac6000rw-p0000000000:000161212121200 7ffff7aca000r-xp0000000008:1146146360960856283856000libstdc++.so.6.0.20 7ffff7bba000---p000f000008:11461463602048000000libstdc++.so.6.0.20 7ffff7dba000r--p000f000008:1146146360323232323200libstdc++.so.6.0.20 7ffff7dc2000rw-p000f800008:11461463608888800libstdc++.so.6.0.20 7ffff7dc4000rw-p0000000000:000841616161600 7ffff7dd9000r-xp0000000008:112663441441441144000ld-2.21.so //程序的內(nèi)存映射區(qū),主要是動(dòng)態(tài)庫(kù)加載到該內(nèi)存區(qū),包括動(dòng)態(tài)庫(kù)的text代碼段和數(shù)據(jù)data段。 //中間沒(méi)有名字的,屬于程序的匿名映射段,主要提供大內(nèi)存分配。 7ffff7fd4000rw-p0000000000:000202020202000 7ffff7ff5000rw-p0000000000:000121212121200 7ffff7ff8000r--p0000000000:0008000000[vvar] 7ffff7ffa000r-xp0000000000:0008404000[vdso] //vvar page,kernel的一些系統(tǒng)調(diào)用的數(shù)據(jù)會(huì)映射到這個(gè)頁(yè)面,用戶(hù)可以直接在用戶(hù)空間訪問(wèn); //vDSO -virtual dynamic shared object,is a small shared library exported by the kernel to accelerate the execution of certain system calls that do not necessarily have to run in kernel space, 就是內(nèi)核實(shí)現(xiàn)了glibc的一些系統(tǒng)調(diào)用,然后可以直接在用戶(hù)空間執(zhí)行,提高系統(tǒng)調(diào)用效率和減少與glibc的耦合。 7ffff7ffc000r--p0002300008:112663444444400ld-2.21.so 7ffff7ffd000rw-p0002400008:112663444444400ld-2.21.so 7ffff7ffe000rw-p0000000000:0004444400 7ffffffde000rw-p0000000000:000136888800[stack] //此段為程序的棧區(qū) ffffffffff600000r-xp0000000000:0004000000[vsyscall] //此段是Linux實(shí)現(xiàn)vsyscall系統(tǒng)調(diào)用vsyscall庫(kù)代碼段 ========================================= 127442644489264417200KB

思考問(wèn)題:

1 棧為什么要由高地址向低地址擴(kuò)展,堆為什么由低地址向高地址擴(kuò)展?

歷史原因:在沒(méi)有MMU的時(shí)代,為了最大的利用內(nèi)存空間,堆和棧被設(shè)計(jì)為從兩端相向生長(zhǎng)。那么哪一個(gè)向上,哪一個(gè)向下呢?人們對(duì)數(shù)據(jù)訪問(wèn)是習(xí)慣于向上的,比如你在堆中new一個(gè)數(shù)組,是習(xí)慣于把低元素放到低地址,把高位放到高地址,所以堆 向上生長(zhǎng)比較符合習(xí)慣, 而棧則對(duì)方向不敏感,一般對(duì)棧的操作只有PUSH和pop,無(wú)所謂向上向下,所以就把堆放在了低端,把棧放在了高端. 但現(xiàn)在已經(jīng)習(xí)慣這樣了。這個(gè)和處理器設(shè)計(jì)有關(guān)系,目前大多數(shù)主流處理器都是這樣設(shè)計(jì),但ARM 同時(shí)支持這兩種增長(zhǎng)方式。

2 如何查看進(jìn)程虛擬地址空間的使用情況?

3 對(duì)比堆和棧優(yōu)缺點(diǎn)?

四 C++棧內(nèi)存空間模型

C++程序運(yùn)行調(diào)用棧示意圖:

fe52fd5e-7243-11eb-8b86-12bb97331649.jpg

函數(shù)調(diào)用過(guò)程中,棧(有俗稱(chēng)堆棧)的變化:

fe88ad3c-7243-11eb-8b86-12bb97331649.jpg

fromhttps://zhuanlan.zhihu.com/p/25816426

當(dāng)主函數(shù)調(diào)用子函數(shù)的時(shí)候:

在主函數(shù)中,將子函數(shù)的參數(shù)按照一定調(diào)用約定(參考調(diào)用約定),一般是從右向左把參數(shù)push到棧中;

然后把下一條指令地址,即返回地址(return address)push入棧(隱藏在call指令中);

然后跳轉(zhuǎn)到子函數(shù)地址處執(zhí)行:call 子函數(shù);此時(shí)

2. 子函數(shù)執(zhí)行:

push %rbp : 把當(dāng)前rbp的值保持在棧中;

mov %rsp, %rbp:把rbp移到最新棧頂位置,即開(kāi)啟子函數(shù)的新幀;

[可選]sub $xxx, %esp:在棧上分配XXX字節(jié)的臨時(shí)空間。(抬高棧頂)(編譯器根據(jù)函數(shù)中的局部變量的總大小確定臨時(shí)空間的大小);

[可選]push XXX: 保存(push)一些寄存器的值;

3. 子函數(shù)調(diào)用返回:

保持返回值:一般將函數(shù)函數(shù)值保持在eax寄存器中;

[可選]恢復(fù)(pop)一些寄存器的值;

mov %rbp,%rsp: 收回??臻g,恢復(fù)主函數(shù)的棧頂;

pop %rbp;恢復(fù)主函數(shù)的棧底;

在AT&T中:

以上兩條指令可以被leave指令取代

leave

ret;從棧頂獲取之前保持的返回地址(return address),并跳轉(zhuǎn)到此位置執(zhí)行;

棧攻擊

由上面棧內(nèi)存布局可以看出,棧很容易被破壞和攻擊,通過(guò)棧緩沖器溢出攻擊,用攻擊代碼首地址來(lái)替換函數(shù)幀的返回地址,當(dāng)子函數(shù)返回時(shí),便跳轉(zhuǎn)到攻擊代碼處執(zhí)行,獲取系統(tǒng)的控制權(quán),所以操作系統(tǒng)和編譯器采用了一些常用的防攻擊的方法:

ASLR(地址空間布局隨機(jī)化):操作系統(tǒng)可以將函數(shù)調(diào)用棧的起始地址設(shè)為隨機(jī)化(這種技術(shù)被稱(chēng)為內(nèi)存布局隨機(jī)化,即Address Space Layout Randomization (ASLR) ),加大了查找函數(shù)地址及返回地址的難度。

Cannary

gcc關(guān)于棧溢出檢測(cè)的幾個(gè)參數(shù):

開(kāi)啟Canary之后,函數(shù)開(kāi)始時(shí)在ebp和臨時(shí)變量之間插入一個(gè)隨機(jī)值,函數(shù)結(jié)束時(shí)驗(yàn)證這個(gè)值。如果不相等(也就是這個(gè)值被其他值覆蓋了),就會(huì)調(diào)用 _stackchk_fail函數(shù),終止進(jìn)程。對(duì)應(yīng)GCC編譯選項(xiàng)-fno-stack-protector解除該保護(hù)。

NX.
開(kāi)啟NX保護(hù)之后,程序的堆棧將會(huì)不可執(zhí)行。對(duì)應(yīng)GCC編譯選項(xiàng)-z execstack解除該保護(hù)。

棧異常處理

一個(gè)函數(shù)(或方法)拋出異常,那么它首先將當(dāng)前棧上的變量全部清空(unwinding),如果變量是類(lèi)對(duì)象的話,將調(diào)用其析構(gòu)函數(shù),接著,異常來(lái)到call stack的上一層,做相同操作,直到遇到catch語(yǔ)句。

指針是一個(gè)普通的變量,不是類(lèi)對(duì)象,所以在清空call stack時(shí),指針指向資源的析構(gòu)函數(shù)將不會(huì)調(diào)用。

思考問(wèn)題:

1 遞歸調(diào)用函數(shù)怎么從20層直接返回到17層,程序可以正常運(yùn)行?

參考上面棧幀的結(jié)構(gòu),中心思想是當(dāng)遞歸函數(shù)執(zhí)行到第20層的時(shí)候,把當(dāng)前棧幀的rbp值替換為17層的rbp的值, 怎么得到17層rbp的值, 就是通過(guò)反復(fù)取rbp的值(rbp保持了上一幀的rbp),

核心代碼如下:

/*changestack*/ intret_stack(intlayer) { unsignedlongrbp=0; unsignedlonglayer_rbp=0; intdepth=0; /*1.得到首層函數(shù)的?;?/ __asm__volatile( "movq%%rbp,%0 " :"=r"(rbp) : :"memory"); layer_rbp=rbp; cout<

2調(diào)用約定有哪些?

我們最常用是以下幾種約定

ff0ce6ec-7243-11eb-8b86-12bb97331649.png


1. cdec

?是c/c++默認(rèn)的調(diào)用約定
2. stdcall

它是微軟Win32 API的一準(zhǔn)標(biāo)準(zhǔn),我們常用的回調(diào)函數(shù)就是通過(guò)這種調(diào)用方式

3.thiscall

thiscall 是c++中非靜態(tài)類(lèi)成員函數(shù)的默認(rèn)調(diào)用約定

五 C++堆內(nèi)存空間模型

1. C++ 程序動(dòng)態(tài)申請(qǐng)內(nèi)存new/delete:

new/delete 操作符,C++內(nèi)置操作符

1. new操作符做兩件事,分配內(nèi)存+調(diào)用構(gòu)造函數(shù)初始化。你不能改變它的行為;

2. delete操作符同樣做兩件事,調(diào)用析構(gòu)函數(shù)+釋放內(nèi)存。你不能改變它的行為;

operator new/delete 函數(shù)

operator new :

The defaultallocation and deallocation functionsare special components of the standard library; They have the following unique properties:

Global:All three versions ofoperator neware declared in the global namespace, not within thestdnamespace.

Implicit:The allocating versions ((1)and(2)) areimplicitly declaredin every translation unit of a C++ program, no matter whether headeris included or not.

Replaceable: The allocating versions ((1)and(2)) are alsoreplaceable: A program may provide its own definition that replaces the one provided by default to produce the result described above, or can overload it for specific types.

Ifset_new_handlerhas been used to define anew_handlerfunction, thisnew-handlerfunction is called by the default definitions of the allocating versions ((1)and(2)) if they fail to allocate the requested storage.

operator newcan be called explicitly as a regular function, but in C++,newis an operator with a very specific behavior: An expression with thenewoperator, first calls functionoperator new(i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.

fromhttp://www.cplusplus.com/reference/new/operator%20new/

1.是用來(lái)專(zhuān)門(mén)分配內(nèi)存的函數(shù),為new操作符調(diào)用,你能增加額外的參數(shù)重載函數(shù)operator new(有限制):

限制1:第一個(gè)參數(shù)類(lèi)型必須是size_t;

限制2:函數(shù)必須返回void*;

2.operator new 底層一般調(diào)用malloc函數(shù)(gcc+glibc)分配內(nèi)存;

3.operator new 分配失敗會(huì)拋異常(默認(rèn)),通過(guò)傳遞參數(shù)也可以不拋異常,返回空指針;

operator delete :

1.是用來(lái)專(zhuān)門(mén)分配內(nèi)存的函數(shù),為delete操作符調(diào)用,你能增加額外的參數(shù)重載函數(shù)operator delete(有限制):

限制1:第一個(gè)參數(shù)類(lèi)型必須是void*;

限制2:函數(shù)必須返回void;

2.operator delete底層一般調(diào)用free函數(shù)(gcc+glibc)釋放內(nèi)存;

3.operator delete分配失敗會(huì)拋異常(默認(rèn)),通過(guò)傳遞參數(shù)也可以不拋異常,返回空指針;

placement new/delete 函數(shù)

1. placement new 其實(shí)就是new的一種重載,placement new是一種特殊的operator new,作用于一塊已分配但未處理或未初始化的raw內(nèi)存,就是用一塊已經(jīng)分配好的內(nèi)存上重建對(duì)象(調(diào)用構(gòu)造函數(shù));

2. 它是C++庫(kù)標(biāo)準(zhǔn)的一部分;

3. placement delete 什么都不做;

4.數(shù)組分配 new[]/delete[] 表達(dá)式

對(duì)應(yīng)會(huì)調(diào)用operator new[]/delete[]函數(shù);

按對(duì)象的個(gè)數(shù),分別調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù);

http://www.cplusplus.com/reference/new/operator%20new[]/

class-specific allocation functions(成員函數(shù))

ff6df93c-7243-11eb-8b86-12bb97331649.png


http://en.cppreference.com/w/cpp/memory/new/operator_new

定制對(duì)象特殊new/delete函數(shù);

實(shí)現(xiàn)一般是使用全局:

::operatornew

::operatordelete

關(guān)鍵點(diǎn):

你想在堆上建立一個(gè)對(duì)象,應(yīng)該用new操作符。它既分配內(nèi)存又為對(duì)象調(diào)用構(gòu)造函數(shù)。

如果你僅僅想分配內(nèi)存,就應(yīng)該調(diào)用operator new函數(shù);它不會(huì)調(diào)用構(gòu)造函數(shù)。

如果你想定制自己的在堆對(duì)象被建立時(shí)的內(nèi)存分配過(guò)程,你應(yīng)該寫(xiě)你自己的operator new函數(shù),然后使用new操作符,new操作符會(huì)調(diào)用你定制的operator new。

如果你想在一塊已經(jīng)獲得指針的內(nèi)存里建立一個(gè)對(duì)象,應(yīng)該用placement new。

C++可以為分配失敗設(shè)置自己的異常處理函數(shù):

If set_new_handler has been used to define a new_handlerfunction, thisnew-handlerfunction is called by the default definitions of the allocating versions ((1)and(2)) if they fail to allocate the requested storage.

如果在構(gòu)造函數(shù)時(shí)候拋出異常,new表達(dá)式后面會(huì)調(diào)用對(duì)應(yīng)operator delete函數(shù)釋放內(nèi)存:

The other signatures ((2)and(3)) arenevercalled by adelete-expression(thedeleteoperator always calls the ordinary version of this function, and exactly once for each of its arguments). These other signatures are only called automatically by anew-expressionwhen their object construction fails (e.g., if the constructor of an object throws while being constructed by anew-expressionwithnothrow, the matchingoperator deletefunction accepting anothrowargument is called).

思考問(wèn)題:

1 malloc和free是怎么實(shí)現(xiàn)的?

2 malloc 分配多大的內(nèi)存,就占用多大的物理內(nèi)存空間嗎?

3 free 的內(nèi)存真的釋放了嗎(還給 OS ) ?

4 既然堆內(nèi)內(nèi)存不能直接釋放,為什么不全部使用 mmap 來(lái)分配?

5 如何查看堆內(nèi)內(nèi)存的碎片情況?

6 除了 glibc 的 malloc/free ,還有其他第三方實(shí)現(xiàn)嗎?

2. C++11的智能指針與垃圾回收

C++智能指針出現(xiàn)是為了解決由于支持動(dòng)態(tài)內(nèi)存分配而導(dǎo)致的一些C++內(nèi)存問(wèn)題,比如內(nèi)存泄漏,對(duì)象生命周期的管理,懸掛指針(dangling pointer)/空指針等問(wèn)題;

C++智能指針通過(guò)RAII設(shè)計(jì)模式去管理對(duì)象生命周期(動(dòng)態(tài)內(nèi)存管理),提供帶少量異常類(lèi)似普通指針的操作接口,在對(duì)象構(gòu)造的時(shí)候分配內(nèi)存,在對(duì)象作用域之外釋放內(nèi)存,幫助程序員管理動(dòng)態(tài)內(nèi)存;

老的智能指針auto_ptr由于設(shè)計(jì)語(yǔ)義不好而導(dǎo)致很多不合理問(wèn)題:不支持復(fù)制(拷貝構(gòu)造函數(shù))和賦值(operator =),但復(fù)制或賦值的時(shí)候不會(huì)提示出錯(cuò)。因?yàn)椴荒鼙粡?fù)制,所以不能被放入容器中。而被C++11棄用(deprecated);

新的智能指針:

1. shared_ptr

shared_ptr是引用計(jì)數(shù)型(reference counting)智能指針, shared_ptr包含兩個(gè)成員,一個(gè)是指向真正數(shù)據(jù)的指針,另一個(gè)是引用計(jì)數(shù)ref_count模塊指針,對(duì)比GCC實(shí)現(xiàn),大致原理如下,

ffd71fd4-7243-11eb-8b86-12bb97331649.png

共享對(duì)象(數(shù)據(jù))(賦值拷貝),引用計(jì)數(shù)加1,指針消亡,引用計(jì)數(shù)減1,當(dāng)引用計(jì)數(shù)為0,自動(dòng)析構(gòu)所指的對(duì)象,引用計(jì)數(shù)是線程安全的(原子操作)。

shared_ptr關(guān)鍵點(diǎn):

用shared_ptr就不要new,保證內(nèi)存管理的一致性;

使用weak_ptr來(lái)打破循環(huán)引用;

用make_shared來(lái)生成shared_ptr,提高效率,內(nèi)存分配一次搞定,防止異常導(dǎo)致內(nèi)存泄漏,參考https://herbsutter.com/gotw/_102/;

大量的shared_ptr會(huì)導(dǎo)致程序性能下降(相對(duì)其他指針),需要等到所有的weak引用為0時(shí)才能最終釋放內(nèi)存(delete);

用enable_shared_from_this來(lái)使一個(gè)類(lèi)能獲取自身的shared_ptr;

不能在對(duì)象的構(gòu)造函數(shù)中使用shared_from_this()函數(shù),因?yàn)閷?duì)象還沒(méi)有構(gòu)造完畢,share_ptr還沒(méi)有初始化構(gòu)造完全;構(gòu)造順序:先需要調(diào)用enable_shared_from_this類(lèi)的構(gòu)造函數(shù),接著調(diào)用對(duì)象的構(gòu)造函數(shù),最后需要調(diào)用shared_ptr類(lèi)的構(gòu)造函數(shù)初始化enable_shared_from_this的成員變量weak_this_。然后才能使用shared_from_this()函數(shù);

2. unique_ptr

獨(dú)占指針,不共享,不能賦值拷貝;

unique_ptr關(guān)鍵點(diǎn):

1. 如果對(duì)象不需要共享,一般最好都用unique_ptr,性能好,更安全;

2. 可以通過(guò)move語(yǔ)義傳遞對(duì)象的生命周期控制權(quán);

3. 函數(shù)可以返回unique_ptr對(duì)象,為什么?

RVO和NRVO

當(dāng)函數(shù)返回一個(gè)對(duì)象時(shí),理論上會(huì)產(chǎn)生臨時(shí)變量,那必然是會(huì)導(dǎo)致新對(duì)象的構(gòu)造和舊對(duì)象的析構(gòu),這對(duì)效率是有影響的。C++編譯針對(duì)這種情況允許進(jìn)行優(yōu)化,哪怕是構(gòu)造函數(shù)有副作用,這叫做返回值優(yōu)化(RVO),返回有名字的對(duì)象叫做具名返回值優(yōu)化(NRVO),就那RVO來(lái)說(shuō)吧,本來(lái)是在返回時(shí)要生成臨時(shí)對(duì)象的,現(xiàn)在構(gòu)造返回對(duì)象時(shí)直接在接受返回對(duì)象的空間中構(gòu)造了。假設(shè)不進(jìn)行返回值優(yōu)化,那么上面返回unique_ptr會(huì)不會(huì)有問(wèn)題呢?也不會(huì)。因?yàn)闃?biāo)準(zhǔn)允許編譯器這么做:

1.如果支持move構(gòu)造,那么調(diào)用move構(gòu)造。

2.如果不支持move,那就調(diào)用copy構(gòu)造。

3.如果不支持copy,那就報(bào)錯(cuò)吧。

顯然的,unique_ptr是支持move構(gòu)造的,unique_ptr對(duì)象可以被函數(shù)返回。

3. weak_ptr

引用對(duì)象,不增加引用計(jì)數(shù),對(duì)象生命周期,無(wú)法干預(yù);

配合shared_ptr解決shared_ptr循環(huán)引用問(wèn)題;

可以影響到對(duì)象內(nèi)存最終釋放的時(shí)間;

更詳細(xì)參考:

http://en.cppreference.com/w/cpp/memory/shared_ptr

思考問(wèn)題:

1 C++的賦值和Java的有什么區(qū)別?

C++的賦值可以是對(duì)象拷貝也可以對(duì)象引用,java的賦值是對(duì)象引用;

2 smart_ptr有哪些坑可以仍然導(dǎo)致內(nèi)存泄漏?

2.1.shared_ptr初始化構(gòu)造函數(shù)指針,一般是可以動(dòng)態(tài)管理的內(nèi)存地址,如果不是就可能導(dǎo)致內(nèi)存泄漏;

2.2.shared_ptr要求內(nèi)部new和delete實(shí)現(xiàn)必須是成對(duì),一致性,如果不是就可能導(dǎo)致內(nèi)存泄漏;

2.3. shared_ptr對(duì)象和其他大多數(shù)STL容器一樣,本身不是線程安全的,需要用戶(hù)去保證;

3 unique_ptr有哪些限制?

只能移動(dòng)賦值轉(zhuǎn)移數(shù)據(jù),不能拷貝;

不支持類(lèi)型轉(zhuǎn)換(cast);

4 智能指針是異常安全的嗎?

所謂異常安全是指,當(dāng)異常拋出時(shí),帶有異常安全的函數(shù)會(huì):

不泄露任何資源

不允許數(shù)據(jù)被破壞

智能指針就是采用RAII技術(shù),即以對(duì)象管理資源來(lái)防止資源泄漏。

Exception Safety

Several functions in these smart pointer classes are specified as having "no effect" or "no effect except such-and-such" if an exception is thrown. This means that when an exception is thrown by an object of one of these classes, the entire program state remains the same as it was prior to the function call which resulted in the exception being thrown. This amounts to a guarantee that there are no detectable side effects. Other functions never throw exceptions. The only exception ever thrown by functions which do throw (assumingTmeets the common requirements) isstd::bad_alloc, and that is thrown only by functions which are explicitly documented as possibly throwingstd::bad_alloc.

https://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/smart_ptr.htm

5 智能指針是線程安全的嗎?

智能指針對(duì)象的引用計(jì)數(shù)模塊是線程安全的,因?yàn)?shared_ptr 有兩個(gè)數(shù)據(jù)成員,讀寫(xiě)操作不能原子化,所以對(duì)象本身不是線程安全的,需要用戶(hù)去保證線程安全。

Thread Safety

shared_ptrobjects offer the same level of thread safety as built-in types. Ashared_ptrinstance can be "read" (accessed using only const operations) simultaneously by multiple threads. Differentshared_ptrinstances can be "written to" (accessed using mutable operations such asoperator=orreset) simultaneously by multiple threads (even when these instances are copies, and share the same reference count underneath.)

Any other simultaneous accesses result in undefined behavior.

https://www.boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_thread_safety

C++標(biāo)準(zhǔn)垃圾回收

C++11 提供最小垃圾支持

declare_reachable undeclare_reachable declare_no_pointers undeclare_no_pointers pointer_safety get_pointer_safety

由于很多場(chǎng)景受限,當(dāng)前幾乎沒(méi)有人使用;

感興趣可以參考:

http://www.stroustrup.com/C++11FAQ.html#gc-abi

http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2008/n2585.pdf

思考問(wèn)題:

1 C++可以通過(guò)哪些技術(shù)來(lái)支持“垃圾回收”?

smart_ptr,RAII, move語(yǔ)義等;

2 RAII是指什么?

RAII是指ResourceAcquisitionIsInitialization的設(shè)計(jì)模式,

RAII要求,資源的有效期與持有資源的對(duì)象的生命期嚴(yán)格綁定,即由對(duì)象的構(gòu)造函數(shù)完成資源的分配(獲取),同時(shí)由析構(gòu)函數(shù)完成資源的釋放。在這種要求下,只要對(duì)象能正確地析構(gòu),就不會(huì)出現(xiàn)資源泄露問(wèn)題。)

當(dāng)一個(gè)函數(shù)需要通過(guò)多個(gè)局部變量來(lái)管理資源時(shí),RAII就顯得非常好用。因?yàn)橹挥斜粯?gòu)造成功(構(gòu)造函數(shù)沒(méi)有拋出異常)的對(duì)象才會(huì)在返回時(shí)調(diào)用析構(gòu)函數(shù),同時(shí)析構(gòu)函數(shù)的調(diào)用順序恰好是它們構(gòu)造順序的反序,這樣既可以保證多個(gè)資源(對(duì)象)的正確釋放,又能滿足多個(gè)資源之間的依賴(lài)關(guān)系。

由于RAII可以極大地簡(jiǎn)化資源管理,并有效地保證程序的正確和代碼的簡(jiǎn)潔,所以通常會(huì)強(qiáng)烈建議在C++中使用它。

fromhttps://zh.wikipedia.org/wiki/RAII

3. C++ STL 內(nèi)存模型

STL(C++標(biāo)準(zhǔn)模板庫(kù))引入的一個(gè)Allocator概念。整個(gè)STL所有組件的內(nèi)存均從allocator分配。也就是說(shuō),STL并不推薦使用 new/delete 進(jìn)行內(nèi)存管理,而是推薦使用allocator。

SGI STL allocator總體設(shè)計(jì):

0007b9d2-7244-11eb-8b86-12bb97331649.png

對(duì)象的構(gòu)造和析構(gòu)采用placement new函數(shù):

0043ff1e-7244-11eb-8b86-12bb97331649.png

內(nèi)存配置:

009f0eb8-7244-11eb-8b86-12bb97331649.png

分配算法

00dab896-7244-11eb-8b86-12bb97331649.png

思考問(wèn)題:

1. vector內(nèi)存設(shè)計(jì)和array的區(qū)別和適用的場(chǎng)景?

2. 遍歷map與遍歷vector哪個(gè)更快,為什么?

3. STL的map和unordered_map內(nèi)存設(shè)計(jì)各有什么不同?

六 C++內(nèi)存問(wèn)題及常用的解決方法

1. 內(nèi)存管理功能問(wèn)題

由于C++語(yǔ)言對(duì)內(nèi)存有主動(dòng)控制權(quán),內(nèi)存使用靈活和效率高,但代價(jià)是不小心使用就會(huì)導(dǎo)致以下內(nèi)存錯(cuò)誤:

? memory overrun:寫(xiě)內(nèi)存越界
? double free:同一塊內(nèi)存釋放兩次
? use after free:內(nèi)存釋放后使用
? wild free:釋放內(nèi)存的參數(shù)為非法值
? access uninitialized memory:訪問(wèn)未初始化內(nèi)存
? read invalid memory:讀取非法內(nèi)存,本質(zhì)上也屬于內(nèi)存越界
? memory leak:內(nèi)存泄露
? use after return:caller訪問(wèn)一個(gè)指針,該指針指向callee的棧內(nèi)內(nèi)存
? stack overflow:棧溢出

常用的解決內(nèi)存錯(cuò)誤的方法

代碼靜態(tài)檢測(cè)

靜態(tài)代碼檢測(cè)是指無(wú)需運(yùn)行被測(cè)代碼,通過(guò)詞法分析、語(yǔ)法分析、控制流、數(shù)據(jù)流分析等技術(shù)對(duì)程序代碼進(jìn)行掃描,找出代碼隱藏的錯(cuò)誤和缺陷,如參數(shù)不匹配,有歧義的嵌套語(yǔ)句,錯(cuò)誤的遞歸,非法計(jì)算,可能出現(xiàn)的空指針引用等等。統(tǒng)計(jì)證明,在整個(gè)軟件開(kāi)發(fā)生命周期中,30%至70%的代碼邏輯設(shè)計(jì)和編碼缺陷是可以通過(guò)靜態(tài)代碼分析來(lái)發(fā)現(xiàn)和修復(fù)的。在C++項(xiàng)目開(kāi)發(fā)過(guò)程中,因?yàn)槠錇榫幾g執(zhí)行語(yǔ)言,語(yǔ)言規(guī)則要求較高,開(kāi)發(fā)團(tuán)隊(duì)往往要花費(fèi)大量的時(shí)間和精力發(fā)現(xiàn)并修改代碼缺陷。所以C++靜態(tài)代碼分析工具能夠幫助開(kāi)發(fā)人員快速、有效的定位代碼缺陷并及時(shí)糾正這些問(wèn)題,從而極大地提高軟件可靠性并節(jié)省開(kāi)發(fā)成本。

靜態(tài)代碼分析工具的優(yōu)勢(shì):

1、自動(dòng)執(zhí)行靜態(tài)代碼分析,快速定位代碼隱藏錯(cuò)誤和缺陷。

2、幫助代碼設(shè)計(jì)人員更專(zhuān)注于分析和解決代碼設(shè)計(jì)缺陷。

3、減少在代碼人工檢查上花費(fèi)的時(shí)間,提高軟件可靠性并節(jié)省開(kāi)發(fā)成本。

一些主流的靜態(tài)代碼檢測(cè)工具,免費(fèi)的cppcheck,clang static analyzer;

商用的coverity,pclint等

各個(gè)工具性能對(duì)比:

http://www.51testing.com/html/19/n-3709719.html

代碼動(dòng)態(tài)檢測(cè)

所謂的代碼動(dòng)態(tài)檢測(cè),就是需要再程序運(yùn)行情況下,通過(guò)插入特殊指令,進(jìn)行動(dòng)態(tài)檢測(cè)和收集運(yùn)行數(shù)據(jù)信息,然后分析給出報(bào)告。

1.為了檢測(cè)內(nèi)存非法使用,需要hook內(nèi)存分配和操作函數(shù)。hook的方法可以是用C-preprocessor,也可以是在鏈接庫(kù)中直接定義(因?yàn)镚libc中的malloc/free等函數(shù)都是weak symbol),或是用LD_PRELOAD。另外,通過(guò)hook strcpy(),memmove()等函數(shù)可以檢測(cè)它們是否引起buffer overflow。


2. 為了檢查內(nèi)存的非法訪問(wèn),需要對(duì)程序的內(nèi)存進(jìn)行bookkeeping,然后截獲每次訪存操作并檢測(cè)是否合法。bookkeeping的方法大同小異,主要思想是用shadow memory來(lái)驗(yàn)證某塊內(nèi)存的合法性。至于instrumentation的方法各種各樣。有run-time的,比如通過(guò)把程序運(yùn)行在虛擬機(jī)中或是通過(guò)binary translator來(lái)運(yùn)行;或是compile-time的,在編譯時(shí)就在訪存指令時(shí)就加入檢查操作。另外也可以通過(guò)在分配內(nèi)存前后加設(shè)為不可訪問(wèn)的guard page,這樣可以利用硬件(MMU)來(lái)觸發(fā)SIGSEGV,從而提高速度。


3.為了檢測(cè)棧的問(wèn)題,一般在stack上設(shè)置canary,即在函數(shù)調(diào)用時(shí)在棧上寫(xiě)magic number或是隨機(jī)值,然后在函數(shù)返回時(shí)檢查是否被改寫(xiě)。另外可以通過(guò)mprotect()在stack的頂端設(shè)置guard page,這樣棧溢出會(huì)導(dǎo)致SIGSEGV而不至于破壞數(shù)據(jù)。

工具總結(jié)對(duì)比,常用valgrind(檢測(cè)內(nèi)存泄露),gperftools(統(tǒng)計(jì)內(nèi)存消耗)等:

technology CTI DBI DBI CTI Library Library
ARCH x86, ARM, PPC x86, ARM, PPC, MIPS, S390X, TILEGX x86 all(?) all(?) all(?)
OS Linux, OS X, Windows, FreeBSD, Android, iOS Simulator Linux, OS X, Solaris, Android Windows, Linux Linux, Mac(?) All (1) Linux, Windows
Slowdown 2x 20x 10x 2x-40x ? ?
Detects:
Heap OOB yes yes yes yes some some
Stack OOB yes no no some no no
Global OOB yes no no ? no no
UAF yes yes yes yes yes yes
UAR yes(seeAddressSanitizerUseAfterReturn) no no no no no
UMR no (see MemorySanitizer) yes yes ? no no
Leaks yes(see LeakSanitizer) yes yes ? no yes
AddressSanitize Valgrind/Memcheck Dr. Memory Mudflap Guard Page gperftools

BI: dynamic binary instrumentation
CTI: compile-time instrumentation
UMR: uninitialized memory reads
UAF: use-after-free (aka dangling pointer)
UAR: use-after-return
OOB: out-of-bounds
x86: includes 32- and 64-bit.
mudflapwas removed in GCC 4.9, as it has been superseded by AddressSanitizer.
Guard Page: a family of memory error detectors (Electric fenceorDUMAon Linux, Page Heap on Windows, libgmalloc on OS X)
gperftools: various performance tools/error detectors bundled with TCMalloc.Heap checker(leak detector) is only available on Linux.Debug allocatorprovides both guard pages and canaryonlydetectors.values for more precise detection of OOB writes, so it's better than guard page.

https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools

2. C++內(nèi)存管理效率問(wèn)題

內(nèi)存管理可以分為三個(gè)層次

0251c7fa-7244-11eb-8b86-12bb97331649.png

自底向上分別是:

第一層:操作系統(tǒng)內(nèi)核的內(nèi)存管理-虛擬內(nèi)存管理

第二層:glibc層維護(hù)的內(nèi)存管理算法

第三層:應(yīng)用程序從glibc動(dòng)態(tài)分配內(nèi)存后,根據(jù)應(yīng)用程序本身的程序特性進(jìn)行優(yōu)化, 比如SGI STL allocator,使用引用計(jì)數(shù)std::shared_ptr,RAII,實(shí)現(xiàn)應(yīng)用的內(nèi)存池等等。

當(dāng)然應(yīng)用程序也可以直接使用系統(tǒng)調(diào)用從內(nèi)核分配內(nèi)存,自己根據(jù)程序特性來(lái)維護(hù)內(nèi)存,但是會(huì)大大增加開(kāi)發(fā)成本。

2. C++內(nèi)存管理問(wèn)題

頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存碎片化,使內(nèi)存再分配和回收的效率下降;

new/delete分配內(nèi)存在linux下默認(rèn)是通過(guò)調(diào)用glibc的api-malloc/free來(lái)實(shí)現(xiàn)的,而這些api是通過(guò)調(diào)用到linux的系統(tǒng)調(diào)用:

0287ed8a-7244-11eb-8b86-12bb97331649.png

brk()/sbrk()//通過(guò)移動(dòng)Heap堆頂指針brk,達(dá)到增加內(nèi)存目的 mmap()/munmap()//通過(guò)文件影射的方式,把文件映射到mmap區(qū)

分配內(nèi)存

分配內(nèi)存 >DEFAULT_MMAP_THRESHOLD,走mmap,直接調(diào)用mmap系統(tǒng)調(diào)用

其中,DEFAULT_MMAP_THRESHOLD默認(rèn)為128k,可通過(guò)mallopt進(jìn)行設(shè)置。

sbrk/brk系統(tǒng)調(diào)用的實(shí)現(xiàn):分配內(nèi)存是通過(guò)調(diào)節(jié)堆頂?shù)奈恢脕?lái)實(shí)現(xiàn), 堆頂?shù)奈恢檬峭ㄟ^(guò)函數(shù) brk 和 sbrk 進(jìn)行動(dòng)態(tài)調(diào)整,參考例子:

(1) 初始狀態(tài):如圖 (1) 所示,系統(tǒng)已分配 ABCD 四塊內(nèi)存,其中 ABD 在堆內(nèi)分配, C 使用 mmap 分配。為簡(jiǎn)單起見(jiàn),圖中忽略了如共享庫(kù)等文件映射區(qū)域的地址空間。

(2) E=malloc(100k) :分配 100k 內(nèi)存,小于 128k ,從堆內(nèi)分配,堆內(nèi)剩余空間不足,擴(kuò)展堆頂 (brk) 指針。

(3) free(A) :釋放 A 的內(nèi)存,在 glibc 中,僅僅是標(biāo)記為可用,形成一個(gè)內(nèi)存空洞 ( 碎片 ),并沒(méi)有真正釋放。如果此時(shí)需要分配 40k 以?xún)?nèi)的空間,可重用此空間,剩余空間形成新的小碎片。

(4) free(C) :C 空間大于 128K ,使用 mmap 分配,如果釋放 C ,會(huì)調(diào)用 munmap 系統(tǒng)調(diào)用來(lái)釋放,并會(huì)真正釋放該空間,還給 OS ,如圖 (4) 所示。

02bb1606-7244-11eb-8b86-12bb97331649.jpg

所以free的內(nèi)存不一定真正的歸還給OS,隨著系統(tǒng)頻繁地 malloc 和 free ,尤其對(duì)于小塊內(nèi)存,堆內(nèi)將產(chǎn)生越來(lái)越多不可用的碎片,導(dǎo)致“內(nèi)存泄露”。而這種“泄露”現(xiàn)象使用 valgrind 是無(wú)法檢測(cè)出來(lái)的。

03045c44-7244-11eb-8b86-12bb97331649.jpg

綜上,頻繁內(nèi)存分配釋放還會(huì)導(dǎo)致大量系統(tǒng)調(diào)用開(kāi)銷(xiāo),影響效率,降低整體性能;

3. 常用解決上述問(wèn)題的方案

內(nèi)存池技術(shù)

內(nèi)存池方案通常一次從系統(tǒng)申請(qǐng)一大塊內(nèi)存塊,然后基于在這塊內(nèi)存塊可以進(jìn)行不同內(nèi)存策略實(shí)現(xiàn),可以比較好得解決上面提到的問(wèn)題,一般采用內(nèi)存池有以下好處:

1.少量系統(tǒng)申請(qǐng)次數(shù),非常少(幾沒(méi)有) 堆碎片。
2.由于沒(méi)有系統(tǒng)調(diào)用等,比通常的內(nèi)存申請(qǐng)/釋放(比如通過(guò)malloc, new等)的方式快。
3.可以檢查應(yīng)用的任何一塊內(nèi)存是否在內(nèi)存池里。
4.寫(xiě)一個(gè)”堆轉(zhuǎn)儲(chǔ)(Heap-Dump)”到你的硬盤(pán)(對(duì)事后的調(diào)試非常有用)。
5.可以更方便實(shí)現(xiàn)某種內(nèi)存泄漏檢測(cè)(memory-leak detection)。

6.減少額外系統(tǒng)內(nèi)存管理開(kāi)銷(xiāo),可以節(jié)約內(nèi)存;

內(nèi)存管理方案實(shí)現(xiàn)的指標(biāo):

額外的空間損耗盡量少

分配速度盡可能快

盡量避免內(nèi)存碎片

多線程性能好

緩存本地化友好

通用性,兼容性,可移植性,易調(diào)試等

各個(gè)內(nèi)存分配器的實(shí)現(xiàn)都是在以上的各種指標(biāo)中進(jìn)行權(quán)衡選擇.

4. 一些業(yè)界主流的內(nèi)存管理方案

SGI STL allocator

是比較優(yōu)秀的 C++庫(kù)內(nèi)存分配器(細(xì)節(jié)參考上面描述)

ptmalloc

是glibc的內(nèi)存分配管理模塊, 主要核心技術(shù)點(diǎn):

033192fe-7244-11eb-8b86-12bb97331649.png

038c7192-7244-11eb-8b86-12bb97331649.png

Arena-main /thread;支持多線程

Heap segments;for thread arena via by mmap call ;提高管理

chunk/Top chunk/Last Remainder chunk;提高內(nèi)存分配的局部性

bins/fast bin/unsorted bin/small bin/large bin;提高分配效率

ptmalloc的缺陷

后分配的內(nèi)存先釋放,因?yàn)?ptmalloc 收縮內(nèi)存是從 top chunk 開(kāi)始,如果與 top chunk 相鄰的 chunk 不能釋放, top chunk 以下的 chunk 都無(wú)法釋放。

多線程鎖開(kāi)銷(xiāo)大, 需要避免多線程頻繁分配釋放。

內(nèi)存從thread的areana中分配, 內(nèi)存不能從一個(gè)arena移動(dòng)到另一個(gè)arena, 就是說(shuō)如果多線程使用內(nèi)存不均衡,容易導(dǎo)致內(nèi)存的浪費(fèi)。比如說(shuō)線程1使用了300M內(nèi)存,完成任務(wù)后glibc沒(méi)有釋放給操作系統(tǒng),線程2開(kāi)始創(chuàng)建了一個(gè)新的arena, 但是線程1的300M卻不能用了。

每個(gè)chunk至少8字節(jié)的開(kāi)銷(xiāo)很大

不定期分配長(zhǎng)生命周期的內(nèi)存容易造成內(nèi)存碎片,不利于回收。64位系統(tǒng)最好分配32M以上內(nèi)存,這是使用mmap的閾值。

tcmalloc

google的gperftools內(nèi)存分配管理模塊, 主要核心技術(shù)點(diǎn):

03c37d22-7244-11eb-8b86-12bb97331649.png

thread-localcache/periodic garbagecollections/CentralFreeList;提高多線程性能,提高cache利用率

TCMalloc給每個(gè)線程分配了一個(gè)線程局部緩存。小分配可以直接由線程局部緩存來(lái)滿足。需要的話,會(huì)將對(duì)象從中央數(shù)據(jù)結(jié)構(gòu)移動(dòng)到線程局部緩存中,同時(shí)定期的垃圾收集將用于把內(nèi)存從線程局部緩存遷移回中央數(shù)據(jù)結(jié)構(gòu)中:

0407aefc-7244-11eb-8b86-12bb97331649.jpg

2. Thread Specific Free List/size-classes [8,16,32,…32k]: 更好小對(duì)象內(nèi)存分配;

每個(gè)小對(duì)象的大小都會(huì)被映射到170個(gè)可分配的尺寸類(lèi)別中的一個(gè)。例如,在分配961到1024字節(jié)時(shí),都會(huì)歸整為1024字節(jié)。尺寸類(lèi)別這樣隔開(kāi):較小的尺寸相差8字節(jié),較大的尺寸相差16字節(jié),再大一點(diǎn)的尺寸差32字節(jié),如此類(lèi)推。最大的間隔(對(duì)于尺寸 >= ~2K的)是256字節(jié)。一個(gè)線程緩存對(duì)每個(gè)尺寸類(lèi)都包含了一個(gè)自由對(duì)象的單向鏈表

043ab252-7244-11eb-8b86-12bb97331649.jpg

3. The central page heap:更好的大對(duì)象內(nèi)存分配,一個(gè)大對(duì)象的尺寸(> 32K)會(huì)被除以一個(gè)頁(yè)面尺寸(4K)并取整(大于結(jié)果的最小整數(shù)),同時(shí)是由中央頁(yè)面堆來(lái)處理 的。中央頁(yè)面堆又是一個(gè)自由列表的陣列。對(duì)于i < 256而言,第k個(gè)條目是一個(gè)由k個(gè)頁(yè)面組成的自由列表。第256個(gè)條目則是一個(gè)包含了長(zhǎng)度>= 256個(gè)頁(yè)面的自由列表:

04a6e4ae-7244-11eb-8b86-12bb97331649.jpg

4. Spans:

TCMalloc管理的堆由一系列頁(yè)面組成。連續(xù)的頁(yè)面由一個(gè)“跨度”(Span)對(duì)象來(lái)表示。一個(gè)跨度可以是已被分配或者是自由的。如果是自由的,跨度則會(huì)是一個(gè)頁(yè)面堆鏈表中的一個(gè)條目。如果已被分配,它會(huì)是一個(gè)已經(jīng)被傳遞給應(yīng)用程序的大對(duì)象,或者是一個(gè)已經(jīng)被分割成一系列小對(duì)象的一個(gè)頁(yè)面。如果是被分割成小對(duì)象的,對(duì)象的尺寸類(lèi)別會(huì)被記錄在跨度中。

由頁(yè)面號(hào)索引的中央數(shù)組可以用于找到某個(gè)頁(yè)面所屬的跨度。例如,下面的跨度a占據(jù)了2個(gè)頁(yè)面,跨度b占據(jù)了1個(gè)頁(yè)面,跨度c占據(jù)了5個(gè)頁(yè)面最后跨度d占據(jù)了3個(gè)頁(yè)面。

04d70030-7244-11eb-8b86-12bb97331649.jpg

tcmalloc的改進(jìn)

ThreadCache會(huì)階段性的回收內(nèi)存到CentralCache里。解決了ptmalloc2中arena之間不能遷移的問(wèn)題。

Tcmalloc占用更少的額外空間。例如,分配N(xiāo)個(gè)8字節(jié)對(duì)象可能要使用大約8N * 1.01字節(jié)的空間。即,多用百分之一的空間。Ptmalloc2使用最少8字節(jié)描述一個(gè)chunk。

更快。小對(duì)象幾乎無(wú)鎖, >32KB的對(duì)象從CentralCache中分配使用自旋鎖。并且>32KB對(duì)象都是頁(yè)面對(duì)齊分配,多線程的時(shí)候應(yīng)盡量避免頻繁分配,否則也會(huì)造成自旋鎖的競(jìng)爭(zhēng)和頁(yè)面對(duì)齊造成的浪費(fèi)。

jemalloc

FreeBSD的提供的內(nèi)存分配管理模塊, 主要核心技術(shù)點(diǎn):

1.與tcmalloc類(lèi)似,每個(gè)線程同樣在<32KB的時(shí)候無(wú)鎖使用線程本地cache;

2. Jemalloc在64bits系統(tǒng)上使用下面的size-class分類(lèi):
Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
Huge: [4 MiB, 8 MiB, 12 MiB, …]

3. small/large對(duì)象查找metadata需要常量時(shí)間, huge對(duì)象通過(guò)全局紅黑樹(shù)在對(duì)數(shù)時(shí)間內(nèi)查找

4. 虛擬內(nèi)存被邏輯上分割成chunks(默認(rèn)是4MB,1024個(gè)4k頁(yè)),應(yīng)用線程通過(guò)round-robin算法在第一次malloc的時(shí)候分配arena, 每個(gè)arena都是相互獨(dú)立的,維護(hù)自己的chunks, chunk切割pages到small/large對(duì)象。free()的內(nèi)存總是返回到所屬的arena中,而不管是哪個(gè)線程調(diào)用free().

上圖可以看到每個(gè)arena管理的arena chunk結(jié)構(gòu), 開(kāi)始的header主要是維護(hù)了一個(gè)page map(1024個(gè)頁(yè)面關(guān)聯(lián)的對(duì)象狀態(tài)), header下方就是它的頁(yè)面空間。Small對(duì)象被分到一起, metadata信息存放在起始位置。large chunk相互獨(dú)立,它的metadata信息存放在chunk header map中。

5. 通過(guò)arena分配的時(shí)候需要對(duì)arena bin(每個(gè)small size-class一個(gè),細(xì)粒度)加鎖,或arena本身加鎖。并且線程cache對(duì)象也會(huì)通過(guò)垃圾回收指數(shù)退讓算法返回到arena中。

jemalloc的優(yōu)化

Jmalloc小對(duì)象也根據(jù)size-class,但是它使用了低地址優(yōu)先的策略,來(lái)降低內(nèi)存碎片化。

Jemalloc大概需要2%的額外開(kāi)銷(xiāo)。(tcmalloc 1%, ptmalloc最少8B).

Jemalloc和tcmalloc類(lèi)似的線程本地緩存,避免鎖的競(jìng)爭(zhēng) .

相對(duì)未使用的頁(yè)面,優(yōu)先使用dirty page,提升緩存命中。

性能比較

測(cè)試環(huán)境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 開(kāi)啟hyper-threading, 總共32個(gè)vcpu。16個(gè)table,每個(gè)5M row。OLTP_RO測(cè)試包含5個(gè)select查詢(xún):select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges:

facebook的測(cè)試結(jié)果:

服務(wù)器吞吐量分別用6個(gè)malloc實(shí)現(xiàn)的對(duì)比數(shù)據(jù),可以看到tcmalloc和jemalloc最好(tcmalloc這里版本較舊)。

詳細(xì)參考:

https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919

總結(jié)

可以看出tcmalloc和jemalloc性能接近,比ptmalloc性能要好,在多線程環(huán)境使用tcmalloc和jemalloc效果非常明顯。一般支持多核多線程擴(kuò)展情況下可以使用jemalloc;反之使用tcmalloc可能是更好的選擇。

可以參考:

https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/

http://goog-perftools.sourceforge.net/doc/tcmalloc.html

https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919

https://blog.csdn.net/junlon2006/article/details/77854898

思考問(wèn)題:

1 jemalloc和tcmalloc最佳實(shí)踐是什么?

2 內(nèi)心池的設(shè)計(jì)有哪些套路?為什么?

七 C++程序內(nèi)存性能測(cè)試

用系統(tǒng)工具抓取性能數(shù)據(jù)

pmap

通過(guò)讀取/proc/$PID/maps 和 smaps 的數(shù)據(jù),解析數(shù)據(jù),生成進(jìn)程的虛列內(nèi)存映像和一些內(nèi)存統(tǒng)計(jì):

pmap-X-p31931 31931:./bug_tc AddressPermOffsetDeviceInodeSizeRssPssReferencedAnonymousSwapLockedMapping … 7f37e4c36000rw-p0000000000:00013288888088440[heap] 7fffff85c000rw-p0000000000:0007824782078207820782000[stack] … ============================================ 713961654013902165401304800KB

里面可以查看程序堆和棧內(nèi)存大小區(qū)間,程序所占內(nèi)存大小,主要是關(guān)注PSS

以下內(nèi)存統(tǒng)計(jì)名稱(chēng)解釋?zhuān)?/p>

VSS:Virtual Set Size,虛擬內(nèi)存耗用內(nèi)存,包括共享庫(kù)的內(nèi)存;

RSS:Resident Set Size,實(shí)際使用物理內(nèi)存,包括共享庫(kù);

PSS:Proportional Set Size,實(shí)際使用的物理內(nèi)存,共享庫(kù)按比例分配;

USS:Unique Set Size,進(jìn)程獨(dú)占的物理內(nèi)存,不計(jì)算共享庫(kù),也可以理解為將進(jìn)程殺 死能釋放出的內(nèi)存;

一般VSS >= RSS >= PSS >= USS,一般統(tǒng)計(jì)程序的內(nèi)存占用,PSS是最好的選擇,比較合理。

top

實(shí)時(shí)顯示內(nèi)存當(dāng)前使用情況和各個(gè)進(jìn)程使用內(nèi)存信息

free

查看系統(tǒng)可用內(nèi)存和占用情況

/proc/meminfo

查看機(jī)器使用內(nèi)存使用統(tǒng)計(jì)和內(nèi)存硬件基本信息。

vmstat

監(jiān)控內(nèi)存變化

詳細(xì)請(qǐng)參考man手冊(cè):

http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/

思考問(wèn)題:

1 各個(gè)工具優(yōu)缺點(diǎn)和使用場(chǎng)景?

2 linux內(nèi)存統(tǒng)計(jì)里面,劃分了哪些統(tǒng)計(jì)?

參加答案

2.valgrind massif

堆棧分析器,指示程序中使用了多少堆內(nèi)存等信息,可以幫助你減少程序內(nèi)存使用量,因?yàn)楦〕绦蚋芏嗾糲ache,減少分頁(yè),加速程序;對(duì)于需要大量?jī)?nèi)存的程序,可以讓程序能夠減少交換分區(qū)使用,加速程序。

valgrind massif 采集完數(shù)據(jù)生成數(shù)據(jù)文件,數(shù)據(jù)文件會(huì)顯示每一幀的程序使用的堆內(nèi)存大小,

06ae1fe2-7244-11eb-8b86-12bb97331649.png

The Snapshot Details 顯示更多細(xì)節(jié):

06f66f0e-7244-11eb-8b86-12bb97331649.png

更多細(xì)節(jié)參考:

http://valgrind.org/docs/manual/ms-manual.html

3. gperftools--heapprofile

gperftools工具里面的內(nèi)存監(jiān)控器,統(tǒng)計(jì)監(jiān)控程序使用內(nèi)存的多少,可以查看內(nèi)存使用熱點(diǎn),默認(rèn)是100ms一次采樣。

text模式:% pprof --text test_tc test.prof

Total: 38 samples
7 18.4% 18.4% 7 18.4% operator delete[] (inline)
3 7.9% 26.3% 3 7.9% PackedCache::TryGet (inline)
3 7.9% 34.2% 37 97.4% main::{lambda#1}::operator
3 7.9% 42.1% 5 13.2% operator new (inline)
3 7.9% 50.0% 4 10.5% tcmalloc::ReleaseToSpans
2 5.3% 55.3% 2 5.3% SpinLock::SpinLoop
2 5.3% 60.5% 2 5.3% _init
2 5.3% 65.8% 2 5.3% tcmalloc::FetchFromOneSpans
2 5.3% 71.1% 2 5.3% tcmalloc::GetThreadHeap (inline)
2 5.3% 76.3% 2 5.3% tcmalloc::ReleaseToCentralCache (inline)
1 2.6% 78.9% 1 2.6% ProfileData::FlushTable
1 2.6% 81.6% 4 10.5% SpinLock::Lock (inline)
1 2.6% 84.2% 1 2.6% TCMalloc_PageMap2::get (inline)
1 2.6% 86.8% 5 13.2% tcmalloc::ReleaseListToSpans
1 2.6% 89.5% 6 15.8% tcmalloc::RemoveRange
1 2.6% 92.1% 1 2.6% tcmalloc::GetSizeClass (inline)

第一列代表這個(gè)函數(shù)調(diào)用本身直接使用了多少內(nèi)存,

第二列表示第一列的百分比,

第三列是從第一行到當(dāng)前行的所有第二列之和,

第四列表示這個(gè)函數(shù)調(diào)用自己直接使用加上所有子調(diào)用使用的內(nèi)存總和,

第五列是第四列的百分比。

基本上只要知道這些,就能很好的掌握每一時(shí)刻程序運(yùn)行內(nèi)存使用情況了,并且對(duì)比不同時(shí)段的不同profile數(shù)據(jù),可以分析出內(nèi)存走向,進(jìn)而定位熱點(diǎn)和泄漏。

pdf模式:可以把采樣的結(jié)果轉(zhuǎn)換為圖模式,這樣查看更為直觀:

073dfbe4-7244-11eb-8b86-12bb97331649.png

Kcachegrind模式:利用pprof生成callgrind格式的文件即可,KCachegrind的GUI工具,用于分析callgrind:

圖形化地瀏覽源碼和執(zhí)行次數(shù),并使用各種排序來(lái)搜索可優(yōu)化的東西。

分析不同的圖表,來(lái)可視化地觀察什么占據(jù)了大多數(shù)時(shí)間,以及它調(diào)用了什么。

查看真實(shí)的匯編機(jī)器碼輸出,使你能夠看到實(shí)際的指令,給你更多的線索。

可視化地顯示源碼中的循環(huán)和分支的跳躍方式,便于你更容易地找到優(yōu)化代碼的方法。

更多細(xì)節(jié)參考

https://github.com/gperftools/gperftools/blob/master/docs/heapprofile.html

windows 版本:

https://sourceforge.net/projects/precompiledbin/files/latest/download?source=files

思考問(wèn)題:

1 說(shuō)一說(shuō)內(nèi)存對(duì)設(shè)備(手機(jī),PC,嵌入式設(shè)備)性能影響?

責(zé)任編輯:lq

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 編程語(yǔ)言
    +關(guān)注

    關(guān)注

    10

    文章

    1957

    瀏覽量

    38417
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2120

    瀏覽量

    76477
  • 內(nèi)存管理
    +關(guān)注

    關(guān)注

    0

    文章

    169

    瀏覽量

    14750

原文標(biāo)題:C++內(nèi)存管理全景指南

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    五大電磁頻譜管理系統(tǒng):原理、架構(gòu)與應(yīng)用全景解析

    五大電磁頻譜管理系統(tǒng):原理、架構(gòu)與應(yīng)用全景解析
    的頭像 發(fā)表于 09-26 10:21 ?219次閱讀
    五大電磁頻譜<b class='flag-5'>管理</b>系統(tǒng):原理、架構(gòu)與應(yīng)用<b class='flag-5'>全景</b>解析

    靈活高效ZBUFF — C內(nèi)存數(shù)據(jù)操作庫(kù):優(yōu)化內(nèi)存管理的利器

    C語(yǔ)言開(kāi)發(fā)中,高效的內(nèi)存管理是提升程序性能的關(guān)鍵。ZBUFF作為一款靈活高效的內(nèi)存數(shù)據(jù)操作庫(kù),通過(guò)優(yōu)化內(nèi)存分配與釋放機(jī)制,為開(kāi)發(fā)者提供了更
    的頭像 發(fā)表于 08-14 18:01 ?439次閱讀
    靈活高效ZBUFF — <b class='flag-5'>C</b><b class='flag-5'>內(nèi)存</b>數(shù)據(jù)操作庫(kù):優(yōu)化<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>的利器

    在OpenVINO? C++代碼中啟用 AddressSanitizer 時(shí)的內(nèi)存泄漏怎么解決?

    在 OpenVINO? C++代碼中啟用 AddressSanitizer 時(shí)遇到內(nèi)存泄漏: \"#0 0xaaaab8558370 in operator new(unsigned
    發(fā)表于 06-23 07:16

    主流的 MCU 開(kāi)發(fā)語(yǔ)言為什么是 C 而不是 C++?

    在單片機(jī)的地界兒里,C語(yǔ)言穩(wěn)坐中軍帳,C++想分杯羹?難嘍。咱電子工程師天天跟那針尖大的內(nèi)存空間較勁,C++那些花里胡哨的玩意兒,在這兒真玩不轉(zhuǎn)。先說(shuō)
    的頭像 發(fā)表于 05-21 10:33 ?690次閱讀
    主流的 MCU 開(kāi)發(fā)語(yǔ)言為什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    C++學(xué)到什么程度可以找工作?

    C++學(xué)到什么程度可以找工作?要使用C++找到工作,特別是作為軟件開(kāi)發(fā)人員或相關(guān)職位,通常需要掌握以下幾個(gè)方面: 1. **語(yǔ)言基礎(chǔ)**:你需要對(duì)C++的核心概念有扎實(shí)的理解,包括但不限于指針、
    發(fā)表于 03-13 10:19

    Spire.XLS for C++組件說(shuō)明

    Spire.XLS for C++ 是一款專(zhuān)業(yè)的 C++ Excel 組件,可以用在各種 C++ 框架和應(yīng)用程序中。Spire.XLS for C++ 提供了一個(gè)對(duì)象模型 Excel
    的頭像 發(fā)表于 01-14 09:40 ?1151次閱讀
    Spire.XLS for <b class='flag-5'>C++</b>組件說(shuō)明

    EE-112:模擬C++中的類(lèi)實(shí)現(xiàn)

    電子發(fā)燒友網(wǎng)站提供《EE-112:模擬C++中的類(lèi)實(shí)現(xiàn).pdf》資料免費(fèi)下載
    發(fā)表于 01-03 15:15 ?0次下載
    EE-112:模擬<b class='flag-5'>C++</b>中的類(lèi)實(shí)現(xiàn)

    TMS320C6000 DSP增強(qiáng)型直接內(nèi)存訪問(wèn)(EDMA)控制器參考指南

    電子發(fā)燒友網(wǎng)站提供《TMS320C6000 DSP增強(qiáng)型直接內(nèi)存訪問(wèn)(EDMA)控制器參考指南.pdf》資料免費(fèi)下載
    發(fā)表于 12-24 17:24 ?0次下載
    TMS320<b class='flag-5'>C</b>6000 DSP增強(qiáng)型直接<b class='flag-5'>內(nèi)存</b>訪問(wèn)(EDMA)控制器參考<b class='flag-5'>指南</b>

    同樣是函數(shù),在CC++中有什么區(qū)別

    同樣是函數(shù),在 CC++ 中有什么區(qū)別? 第一個(gè)返回值。 C語(yǔ)言的函數(shù)可以不寫(xiě)返回值類(lèi)型,編譯器會(huì)默認(rèn)為返回 int。 但是 C++ 的函數(shù),除了構(gòu)造和析構(gòu)這兩個(gè)特殊的函數(shù),必須
    的頭像 發(fā)表于 11-29 10:25 ?1149次閱讀

    C2000位置管理器BISS-C庫(kù)用戶(hù)指南

    電子發(fā)燒友網(wǎng)站提供《C2000位置管理器BISS-C庫(kù)用戶(hù)指南.pdf》資料免費(fèi)下載
    發(fā)表于 11-09 15:19 ?5次下載
    <b class='flag-5'>C</b>2000位置<b class='flag-5'>管理</b>器BISS-<b class='flag-5'>C</b>庫(kù)用戶(hù)<b class='flag-5'>指南</b>

    C7000 C/C++優(yōu)化指南用戶(hù)手冊(cè)

    電子發(fā)燒友網(wǎng)站提供《C7000 C/C++優(yōu)化指南用戶(hù)手冊(cè).pdf》資料免費(fèi)下載
    發(fā)表于 11-09 15:00 ?0次下載
    <b class='flag-5'>C</b>7000 <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>優(yōu)化<b class='flag-5'>指南</b>用戶(hù)手冊(cè)

    TMS320C6000優(yōu)化C/C++編譯器v8.3.x

    電子發(fā)燒友網(wǎng)站提供《TMS320C6000優(yōu)化C/C++編譯器v8.3.x.pdf》資料免費(fèi)下載
    發(fā)表于 11-01 09:35 ?1次下載
    TMS320<b class='flag-5'>C</b>6000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器v8.3.x

    TMS320C28x優(yōu)化C/C++編譯器v22.6.0.LTS

    電子發(fā)燒友網(wǎng)站提供《TMS320C28x優(yōu)化C/C++編譯器v22.6.0.LTS.pdf》資料免費(fèi)下載
    發(fā)表于 10-31 10:10 ?0次下載
    TMS320<b class='flag-5'>C</b>28x優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器v22.6.0.LTS

    C語(yǔ)言和C++中結(jié)構(gòu)體的區(qū)別

    同樣是結(jié)構(gòu)體,看看在C語(yǔ)言和C++中有什么區(qū)別?
    的頭像 發(fā)表于 10-30 15:11 ?976次閱讀

    C7000優(yōu)化C/C++編譯器

    電子發(fā)燒友網(wǎng)站提供《C7000優(yōu)化C/C++編譯器.pdf》資料免費(fèi)下載
    發(fā)表于 10-30 09:45 ?0次下載
    <b class='flag-5'>C</b>7000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器