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

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

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

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

Linux愛好者 ? 來源:Linux愛好者 ? 作者:Linux愛好者 ? 2021-03-03 15:05 ? 次閱讀

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

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

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

C++的高抽象層次,又兼具高性能,是其他語言所無法替代的,C++標(biāo)準(zhǔn)保持穩(wěn)定發(fā)展,更加現(xiàn)代化,更加強(qiáng)大,更加易用,熟練的 C++ 工程師自然也獲得了“高水平、高薪資”的名聲,但在各種活躍編程語言中,C++門檻依然很高,尤其C++的內(nèi)存問題(內(nèi)存泄露,內(nèi)存溢出,內(nèi)存宕機(jī),堆棧破壞等問題),需要理解C++標(biāo)準(zhǔn)對象模型,C++標(biāo)準(zhǔn)庫,標(biāo)準(zhǔn)C庫,操作系統(tǒng)等內(nèi)存設(shè)計,才能更加深入理解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)庫中引入了memory model,這應(yīng)該是C++11最重要的特性之一了。C++11引入memory model的意義在于我們可以在high level language層面實現(xiàn)對在多處理器中多線程共享內(nèi)存交互的控制。我們可以在語言層面忽略compiler,CPU arch的不同對多線程編程的影響了。我們的多線程可以跨平臺。

內(nèi)存模型

為 C++ 定義計算機(jī)內(nèi)存存儲的語義??捎糜?C++ 程序的內(nèi)存是一或多個相接的字節(jié)序列。內(nèi)存中的每個字節(jié)擁有唯一的地址。

字節(jié)

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

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

內(nèi)存位置

內(nèi)存位置是

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

或非零長位域的最大相接序列

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

線程與數(shù)據(jù)競爭

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

任何線程都能潛在地訪問程序中的任何對象(擁有自動或線程局域存儲期的對象仍可為另一線程通過指針或引用訪問)。

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

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

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

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

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

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

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

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

即使是簡單的語句,C++ 也不保證是原子操作。

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

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

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

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

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 個選項,但它們表示的是四種內(nèi)存模型:

Relaxed ordering

Release-Acquire ordering

Release-Consume ordering

Sequentially-consistent ordering

順序一致次序(sequential consisten ordering)

對應(yīng)memory_order_seq_cst. SC作為默認(rèn)的內(nèi)存序,是因為它意味著將程序看做是一個簡單的序列。如果對于一個原子變量的操作都是順序一致的,那么多線程程序的行為就像是這些操作都以一種特定順序被單線程程序執(zhí)行。從同的角度來看,一個順序一致的 store 操作 synchroniezd-with 一個順序一致的需要讀取相同的變量的 load 操作。除此以外,順序模型還保證了在 load 之后執(zhí)行的順序一致原子操作都得表現(xiàn)得在 store 之后完成。非順序一致內(nèi)存次序(non-sequentially consistency memory ordering)強(qiáng)調(diào)對同一事件(代碼),不同線程可以以不同順序去執(zhí)行,不僅是因為編譯器可以進(jìn)行指令重排,也因為不同的 CPU cache 及內(nèi)部緩存的狀態(tài)可以影響這些指令的執(zhí)行。但所有線程仍需要對某個變量的連續(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()之前的所有讀寫操作,不允許被移動到這個store()的后面。

在load()之后的所有讀寫操作,不允許被移動到這個load()的前面。

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

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

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

思考問題:

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

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

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

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

1 空類對象(一般作為模板的tag來使用)

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

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

2非空類

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

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

3 非空虛基類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 簡單多繼承

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 簡單多繼承-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:多重繼承 (沒有虛繼承)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

注意點:此種繼承存在兩份基類成員,使用時候需要指定路徑,不方便,易出錯。

8The Diamond: 鉆石類虛繼承

解決上面的問題,讓基類只有存在一份,共享基類;

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

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

C++成員非靜態(tài)數(shù)據(jù)需要占用動態(tài)內(nèi)存,棧或者堆中,其他static數(shù)據(jù)存在全局變量區(qū)(數(shù)據(jù)段),編譯時候確定。虛函數(shù)會增加用虛函數(shù)表大小,也是存儲在數(shù)據(jù)區(qū)的.rodada段,編譯時確定,其他函數(shù)不占空間。

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

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

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

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

思考問題:

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ù)?

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

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

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

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

3.基類設(shè)置vptr

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

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

6. 子類設(shè)置vptr

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

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

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

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

三 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)部實現(xiàn)

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

關(guān)鍵點:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5 例子-通過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ū),主要是動態(tài)庫加載到該內(nèi)存區(qū),包括動態(tài)庫的text代碼段和數(shù)據(jù)data段。 //中間沒有名字的,屬于程序的匿名映射段,主要提供大內(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ù)會映射到這個頁面,用戶可以直接在用戶空間訪問; //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)核實現(xiàn)了glibc的一些系統(tǒng)調(diào)用,然后可以直接在用戶空間執(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實現(xiàn)vsyscall系統(tǒng)調(diào)用vsyscall庫代碼段 ========================================= 127442644489264417200KB

思考問題:

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

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

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

3 對比堆和棧優(yōu)缺點?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

在AT&T中:

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

leave

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

棧攻擊

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

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

Cannary

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

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

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

棧異常處理

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

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

思考問題:

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

參考上面棧幀的結(jié)構(gòu),中心思想是當(dāng)遞歸函數(shù)執(zhí)行到第20層的時候,把當(dāng)前棧幀的rbp值替換為17層的rbp的值, 怎么得到17層rbp的值, 就是通過反復(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ù)就是通過這種調(diào)用方式

3.thiscall

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

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

1. C++ 程序動態(tài)申請內(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.是用來專門分配內(nèi)存的函數(shù),為new操作符調(diào)用,你能增加額外的參數(shù)重載函數(shù)operator new(有限制):

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

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

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

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

operator delete :

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

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

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

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

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

placement new/delete 函數(shù)

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

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

3. placement delete 什么都不做;

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

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

按對象的個數(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

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

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

::operatornew

::operatordelete

關(guān)鍵點:

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

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

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

如果你想在一塊已經(jīng)獲得指針的內(nèi)存里建立一個對象,應(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ù)時候拋出異常,new表達(dá)式后面會調(diào)用對應(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).

思考問題:

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

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

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

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

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

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

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

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

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

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

新的智能指針:

1. shared_ptr

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

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

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

shared_ptr關(guān)鍵點:

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

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

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

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

用enable_shared_from_this來使一個類能獲取自身的shared_ptr;

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

2. unique_ptr

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

unique_ptr關(guān)鍵點:

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

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

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

RVO和NRVO

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

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

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

3.如果不支持copy,那就報錯吧。

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

3. weak_ptr

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

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

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

更詳細(xì)參考:

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

思考問題:

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

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

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

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

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

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

3 unique_ptr有哪些限制?

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

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

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

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

不泄露任何資源

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

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

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 智能指針是線程安全的嗎?

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

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

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

感興趣可以參考:

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

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

思考問題:

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

smart_ptr,RAII, move語義等;

2 RAII是指什么?

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

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

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

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

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

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

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

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

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

對象的構(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

思考問題:

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

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

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

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

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

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

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

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

代碼靜態(tài)檢測

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

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

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

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

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

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

商用的coverity,pclint等

各個工具性能對比:

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

代碼動態(tài)檢測

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

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


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


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

工具總結(jié)對比,常用valgrind(檢測內(nèi)存泄露),gperftools(統(tǒng)計內(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)存管理效率問題

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

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

自底向上分別是:

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

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

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

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

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

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

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

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

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

分配內(nèi)存

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

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

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

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

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

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

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

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

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

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

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

3. 常用解決上述問題的方案

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

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

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

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

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

額外的空間損耗盡量少

分配速度盡可能快

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

多線程性能好

緩存本地化友好

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

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

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

SGI STL allocator

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

ptmalloc

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

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)存先釋放,因為 ptmalloc 收縮內(nèi)存是從 top chunk 開始,如果與 top chunk 相鄰的 chunk 不能釋放, top chunk 以下的 chunk 都無法釋放。

多線程鎖開銷大, 需要避免多線程頻繁分配釋放。

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

每個chunk至少8字節(jié)的開銷很大

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

tcmalloc

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

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

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

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

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

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

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

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

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

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

4. Spans:

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

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

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

tcmalloc的改進(jìn)

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

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

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

jemalloc

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

1.與tcmalloc類似,每個線程同樣在<32KB的時候無鎖使用線程本地cache;

2. Jemalloc在64bits系統(tǒng)上使用下面的size-class分類:
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對象查找metadata需要常量時間, huge對象通過全局紅黑樹在對數(shù)時間內(nèi)查找

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

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

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

jemalloc的優(yōu)化

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

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

Jemalloc和tcmalloc類似的線程本地緩存,避免鎖的競爭 .

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

性能比較

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

facebook的測試結(jié)果:

服務(wù)器吞吐量分別用6個malloc實現(xiàn)的對比數(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

思考問題:

1 jemalloc和tcmalloc最佳實踐是什么?

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

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

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

pmap

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

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)計名稱解釋:

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

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

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

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

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

top

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

free

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

/proc/meminfo

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

vmstat

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

詳細(xì)請參考man手冊:

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

思考問題:

1 各個工具優(yōu)缺點和使用場景?

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

參加答案

2.valgrind massif

堆棧分析器,指示程序中使用了多少堆內(nèi)存等信息,可以幫助你減少程序內(nèi)存使用量,因為更小程序更能多占cache,減少分頁,加速程序;對于需要大量內(nèi)存的程序,可以讓程序能夠減少交換分區(qū)使用,加速程序。

valgrind massif 采集完數(shù)據(jù)生成數(shù)據(jù)文件,數(shù)據(jù)文件會顯示每一幀的程序使用的堆內(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)計監(jiān)控程序使用內(nèi)存的多少,可以查看內(nèi)存使用熱點,默認(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)

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

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

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

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

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

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

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

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

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

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

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

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

可視化地顯示源碼中的循環(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

思考問題:

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

責(zé)任編輯:lq

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

    關(guān)注

    10

    文章

    1916

    瀏覽量

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

    關(guān)注

    21

    文章

    2085

    瀏覽量

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

    關(guān)注

    0

    文章

    167

    瀏覽量

    14099

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

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

收藏 人收藏

    評論

    相關(guān)推薦

    C語言中的動態(tài)內(nèi)存管理講解

    本章將講解 C 中的動態(tài)內(nèi)存管理。C 語言為內(nèi)存的分配和管理提供了幾個函數(shù)。這些函數(shù)可以在
    的頭像 發(fā)表于 02-23 14:03 ?311次閱讀
    <b class='flag-5'>C</b>語言中的動態(tài)<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>講解

    無人機(jī)全景監(jiān)測:空域管理的新革命

    隨著科技的飛速發(fā)展,無人機(jī)技術(shù)已成為現(xiàn)代空域管理領(lǐng)域的一股新興力量。無人機(jī)全景監(jiān)測以其高效、精準(zhǔn)的特點,正逐漸成為提升空域管理效率的關(guān)鍵。知語云智能科技在這一領(lǐng)域的前瞻性研究和應(yīng)用,為航空安全和
    發(fā)表于 02-20 15:23

    c語言,c++,java,python區(qū)別

    C語言、C++、Java和Python是四種常見的編程語言,各有優(yōu)點和特點。 C語言: C語言是一種面向過程的編程語言。它具有底層的特性,能夠?qū)τ嬎銠C(jī)硬件進(jìn)行直接操作。
    的頭像 發(fā)表于 02-05 14:11 ?1381次閱讀

    C++簡史:C++是如何開始的

    MISRA C++:2023,MISRA? C++ 標(biāo)準(zhǔn)的下一個版本,來了!為了幫助您做好準(zhǔn)備,我們介紹了 Perforce 首席技術(shù)支持工程師 Frank van den Beuken 博士撰寫
    的頭像 發(fā)表于 01-11 09:00 ?428次閱讀
    <b class='flag-5'>C++</b>簡史:<b class='flag-5'>C++</b>是如何開始的

    C語言和C++中那些不同的地方

    C語言雖說經(jīng)常和C++在一起被大家提起,但可千萬不要以為它們是一個東西?,F(xiàn)在我們常用的C語言是C89標(biāo)準(zhǔn),C++
    的頭像 發(fā)表于 12-07 14:29 ?773次閱讀
    <b class='flag-5'>C</b>語言和<b class='flag-5'>C++</b>中那些不同的地方

    內(nèi)存是如何泄露的

    是如何泄露的 在 C++ 程序中,主要涉及到的內(nèi)存就是『?!缓汀憾选唬ㄆ渌糠植辉诒疚闹薪榻B了)。 通常來說,一個線程的棧內(nèi)存是有限的,通常來說是 8M 左右(取決于運(yùn)行的環(huán)境)。棧上的內(nèi)存
    的頭像 發(fā)表于 11-13 14:13 ?348次閱讀
    <b class='flag-5'>內(nèi)存</b>是如何泄露的

    nginx內(nèi)存池源碼設(shè)計

    造輪子內(nèi)存池原因引入 作為C/C++程序員, 相較JAVA程序員的一個重大特征是我們可以直接訪問內(nèi)存, 自己管理
    的頭像 發(fā)表于 11-13 11:51 ?608次閱讀
    nginx<b class='flag-5'>內(nèi)存</b>池源碼設(shè)計

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

    寫服務(wù)端的,內(nèi)存是一個繞不過的問題,而用C++寫的,這個問題就顯得更嚴(yán)重。進(jìn)程的內(nèi)存持續(xù)上漲,有可能是正常的內(nèi)存占用,也有可能是內(nèi)存碎片,而
    的頭像 發(fā)表于 11-13 11:13 ?516次閱讀
    <b class='flag-5'>C++</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>問題

    Linux 內(nèi)存管理總結(jié)

    一、Linux內(nèi)存管理概述 Linux內(nèi)存管理是指對系統(tǒng)內(nèi)存的分配、釋放、映射、管理、交換、壓縮
    的頭像 發(fā)表于 11-10 14:58 ?431次閱讀
    Linux <b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>總結(jié)

    Linux C/C++編程中的內(nèi)存泄漏問題

    ,需要對各種編程語言和技術(shù)有深入的理解。而C++,作為一種高性能的編程語言,在許多領(lǐng)域(如網(wǎng)絡(luò)編程、嵌入式系統(tǒng)、音視頻處理等)都發(fā)揮著不可忽視的作用。然而,許多C++程序員在編程過程中,尤其是在進(jìn)行復(fù)雜的數(shù)據(jù)結(jié)構(gòu)設(shè)計時,可能會遇到一些棘手的問題,如
    的頭像 發(fā)表于 11-09 10:11 ?784次閱讀
    Linux <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編程中的<b class='flag-5'>內(nèi)存</b>泄漏問題

    靜態(tài)代碼分析工具Helix QAC 2023.3:將100%覆蓋MISRA C++:2023?規(guī)則

    Helix QAC 2023.3預(yù)計將于2023年第四季度發(fā)布的新MISRA C++?指南,將100%覆蓋MISRA C++:2023?規(guī)則。 此外,此版本擴(kuò)展了對C++20語言支持,
    的頭像 發(fā)表于 11-08 18:37 ?434次閱讀
    靜態(tài)代碼分析工具Helix QAC 2023.3:將100%覆蓋MISRA <b class='flag-5'>C++</b>:2023?規(guī)則

    C++之父新作帶你勾勒現(xiàn)代C++地圖

    為了幫助大家解決這些痛點問題,讓大家領(lǐng)略現(xiàn)代C++之美,掌握其中的精髓,更好地使用C++,C++之父Bjarne Stroustrup坐不住了,他親自操刀寫就了這本《C++之旅》!
    的頭像 發(fā)表于 10-30 16:35 ?698次閱讀
    <b class='flag-5'>C++</b>之父新作帶你勾勒現(xiàn)代<b class='flag-5'>C++</b>地圖

    請問mymalloc是管理多個內(nèi)存的嗎?

    C語言自帶的malloc只能管理一個內(nèi)存塊, mymalloc的話,就是管理多個內(nèi)存的嗎? 還有其他的區(qū)別嗎
    發(fā)表于 10-18 07:30

    嵌入式C++內(nèi)存管理的應(yīng)用程序

    說到 C++內(nèi)存管理,我們可能會想到??臻g的本地變量、堆上通過 new 動態(tài)分配的變量以及全局命名空間的變量等,這些變量的分配位置都是由系統(tǒng)來控制管理的,而調(diào)用者只需要考慮變量的生
    發(fā)表于 10-12 10:37 ?254次閱讀
    嵌入式<b class='flag-5'>C++</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>的應(yīng)用程序

    高質(zhì)量C、C++編程指南

    林銳-高質(zhì)量C、C++編程指南電子檔
    發(fā)表于 10-07 07:14