C++內存模型php
一文了解全部C++內存的問題html
AlexCooljava
目錄node
一 C++內存模型linux
二 C++對象內存模型ios
三 C++程序運行內存空間模型c++
四 C++棧內存空間模型git
五 C++堆內存空間模型程序員
六 C++內存問題及經常使用的解決方法github
七 C++程序內存性能測試
環境:
uname -a
Linux alexfeng 3.19.0-15-generic #15-Ubuntu SMP Thu Apr 16 23:32:37 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++ 抽象機的目的定義計算機內存存儲的語義。
可用於 C++ 程序的內存是一或多個相接的字節序列。內存中的每一個字節擁有惟一的地址。
字節是最小的可尋址內存單元。它被定義爲相接的位序列,大到足以保有任何 UTF-8
編碼單元( 256 個相異值)和 (C++14 起)基本執行字符集(要求爲單字節的 96 個字符)的任何成員。相似 C , C++ 支持 8 位或更大的字節。
char 、 unsigned char 和 signed char 類型把一個字節用於存儲和值表示。字節中的位數可做爲 CHAR_BIT 或 std::numeric_limits<unsigned char>::digits 訪問。
內存位置是
注意:各類語言特性,例如引用和虛函數,可能涉及到程序不可訪問,但爲實現所管理的額外內存位置。
執行線程是程序中的控制流,它始於 std::thread::thread 、 std::async 或以其餘方式所作的頂層函數調用。
任何線程都能潛在地訪問程序中的任何對象(擁有自動或線程局域存儲期的對象仍可爲另外一線程經過指針或引用訪問)。
始終容許不一樣的執行線程同時訪問(讀和寫)不一樣的內存位置,而無衝突或同步要求。
一個表達式的求值寫入內存位置,而另外一求值讀或寫同一內存位置時,稱這些表達式衝突。擁有二個衝突求值的程序有數據競爭,除非
若出現數據競爭,則程序的行爲未定義。
(特別是, std::mutex 的釋放同步於,從而先發生於另外一線程取得同一 mutex ,這使得能夠用互斥鎖防止數據競爭)
線程在從內存位置讀取值時,它可能看到初值、同一線程所寫入的值或另外一線程寫入的值。線程所做的寫入對其餘線程變爲可見的順序上的細節,見 std::memory_order 。
from https://zh.cppreference.com/w/cpp/language/memory_model
思考問題:
1 C++正常程序能夠訪問到哪些內存和不能訪問到哪些內存(這些內存屬於該程序)?
2 內存對程序併發執行有什麼影響?
3 std::memory_order 的做用是什麼?
參考答案:
class A { };
sizeof(A) = 1
C++標準要求C++的對象大小不能爲0,C++對象必須在內存裏面有惟一的地址,
但又不想浪費太多內存空間,因此標準規定爲1byte,
A --> +-----------+
| 1 bytes |
+-----------+
class A
{
public:
int a;
};
sizeof(A ) = 8 ,align=8
A --> +-----------+
|pad | a |
+-----------+
class A
{
public:
int a;
virtual void v();
};
sizeof(A ) = 16 ,align=8
vtable
+-----------------------+
| 0 (top_offset) |
+-----------------------+
A --> +----------+ | ptr to typeinfo for A |
| vtptr |-------> +-----------------------+
+----------+ | A::v() |
| pad |a | +-----------------------+
+----------+
class A {
public:
int a;
virtual void v();
};
class B : public A {
public:
int b;
};
sizeof(B) = 16, align = 8
vtable
+-----------------------+
| 0 (top_offset) |
+-----------------------+
b --> +----------+ | ptr to typeinfo for B |
| vtptr |-------> +-----------------------+
+----------+ | A::v() |
| b | a | +-----------------------+
+----------+
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
};
sizeof(C) = 32 ,align = 8
vtable
+-----------------------+
| 0 (top_offset) |
+-----------------------+
c --> +----------+ | ptr to typeinfo for C |
| vtptr |-------> +-----------------------+
+----------+ | A::v() |
| pad |a | +-----------------------+
+----------+ | -16 (top_offset) |
| vtptr |---+ +-----------------------+
+----------+ | | ptr to typeinfo for C |
| c | b | +---> +-----------------------+
+----------+ | B::w() |
+-----------------------+
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
void w();
};
sizeof(C) = 32 ,align = 8
vtable
+-----------------------+
| 0 (top_offset) |
+-----------------------+
c --> +----------+ | ptr to typeinfo for C |
| vtptr |-------> +-----------------------+
+----------+ | A::v() |
| pad |a | +-----------------------+
+----------+ | C::w() |
| vtptr |---+ +-----------------------+
+----------+ | | -16 (top_offset) |
| c | b | | +-----------------------+
+----------+ | | ptr to typeinfo for C |
+---> +-----------------------+
| thunk to C::w() |
+-----------------------+
class A {
public:
int a;
virtual void v();
};
class B : public A {
public:
int b;
virtual void w();
};
class C : public A {
public:
int c;
virtual void x();
};
class D : public B, public C {
public:
int d;
virtual void y();
};
sizeof(D) = 40 align = 8
vtable
+-----------------------+
| 0 (top_offset) |
+-----------------------+
d --> +----------+ | ptr to typeinfo for D |
| vtptr |-------> +-----------------------+
+----------+ | A::v() |
| b |a | +-----------------------+
+----------+ | B::w() |
| vtptr |---+ +-----------------------+
+----------+ | | D::y() |
| c |a | | +-----------------------+
+----------+ | | -16 (top_offset) |
| d | | +-----------------------+
+----------+ | | ptr to typeinfo for D |
+---> +-----------------------+
| A::v() |
+-----------------------+
| C::x() |
+-----------------------+
注意點:
1 此種繼承,存在兩份份基類成員,使用時候須要指定路徑,使用不方便,易出錯。
class A {
public:
int a;
virtual void v();
};
class B : public virtual A {
public:
int b;
virtual void w();
};
class C : public virtual A {
public:
int c;
virtual void x();
};
class D : public B, public C {
public:
int d;
virtual void y();
};
sizeof(D) = 48,align = 8 vtable
+-----------------------+
| 32 (vbase_offset) |
+-----------------------+
| 0 (top_offset) |
+-----------------------+
| ptr to typeinfo for D |
+----------> +-----------------------+
d --> +----------+ | | B::w() |
| vtptr |----+ +-----------------------+
+----------+ | D::y() |
|pad |b | +-----------------------+
+----------+ | 16 (vbase_offset) |
| vtptr |---------+ +-----------------------+
+----------+ | | -16 (top_offset) |
| d | c | | +-----------------------+
+----------+ | | ptr to typeinfo for D |
| vtptr |----+ +-----> +-----------------------+
+----------+ | | C::x() |
| pad | a | | +-----------------------+
+----------+ | | 0 (vbase_offset) |
| +-----------------------+
| | -32 (top_offset) |
| +-----------------------+
| | ptr to typeinfo for D |
+----------> +-----------------------+
| A::v() |
+-----------------------+
注意點:
1 top_offset 表示this指針對子類的偏移,用於子類和繼承類之間dynamic_cast轉換(還須要typeinfo數據),實現多態,
vbase_offset 表示this指針對基類的偏移,用於共享基類;
2 gcc爲了每個類生成一個vtable虛函數表,放在程序的.rodata段,其餘編譯器(平臺)好比vs,實現不太同樣.
3 gcc還有VTT表,裏面存放了各個基類之間虛函數表的關係,最大化利用基類的虛函數表,專門用來爲構建最終類vtable;
4 在構造函數裏面設置對象的vtptr指針。
4 虛函數表地址的前面設置了一個指向type_info的指針,RTTI(Run Time Type Identification)運行時類型識別是有編譯器在編譯器生成的特殊類型信息,包括對象繼承關係,對象自己的描述,RTTI是爲多態而生成的信息,因此只有具備虛函數的對象在會生成。
5 在C++類中有兩種成員數據:static、nonstatic;三種成員函數:static、nonstatic、virtual。
C++成員非靜態數據須要佔用動態內存,棧或者堆中,其餘static數據存在全局變量區(數據段),編譯時候肯定。
虛函數會增長用虛函數表大小,也是存儲在數據區的.rodada段,編譯時肯定,其餘函數不佔空間。
6 G++ 選項 -fdump-class-hierarchy 能夠生成C++類層結構,虛函數表結構,VTT表結構。
7 GDB調試選項:
set p obj <on/off> 在C++中,若是一個對象指針指向其派生類,若是打開這個選項,GDB會如今類對象結構的規則顯示輸出。
set p pertty <on/off>: 按照層次打印結構體。
思考問題:
1 Why don't we have virtual constructors?
2 爲何不要在構造函數或者析構函數中調用虛函數?
3 C++對象構造順序?
4 爲何虛函數會下降效率?
參考答案:
1 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
對於構造函數:此時子類的對象尚未徹底構造,編譯器會去虛函數化,只會用當前類的函數, 若是是純虛函數,就會調用到純虛函數,會致使構造函數拋異常:
pure virtual method calle;
對於析構函數: 一樣,因爲對象不完整,編譯器會去虛函數化,函數調用本類的虛函數,若是本類虛函數是純虛函數,就會到帳析構函數拋出異常:
pure virtual method called;
3 構造大體順序:
1.構造子類構造函數的參數
2.子類調用基類構造函數
3.基類設置vptr
4.基類初始化列表內容進行構造
5. 基類函數體調用
6. 子類設置vptr
7. 子類初始化列表內容進行構造
8. 子類構造函數體調用
4 是由於虛函數調用,執行過程當中會跳轉兩次,首先找到虛函數表,而後再查找對應函數地址,這樣CPU指令就會跳轉兩次,而普通函數指跳轉一次,CPU每跳轉一次,預取指令均可能做廢,這會致使分支預測失敗,流水線排空,因此效率會變低。
設想一下,若是說不是虛函數,那麼在編譯時期,其相對地址是肯定的,編譯器能夠直接生成jmp/invoke指令; 若是是虛函數,多出來的一次查找vtable所帶來的開銷,卻是次要的,關鍵在於,這個函數地址是動態的,譬如 取到的地址在eax裏,則在call eax以後的那些已經被預取進入流水線的全部指令都將失效。流水線越長,一次分支預測失敗的代價也就越大。
32位:
from https://manybutfinite.com/post/anatomy-of-a-program-in-memory/
64位:
from http://www.cnhalo.net/2016/06/13/memory-optimize/
from http://www.javashuo.com/article/p-slspvzbc-bz.html
1 各個分區的意義:
address sizes : 36 bits physical, 48 bits virtual,總的虛擬地址空間爲256TB( 2^48 ),在這256TB的虛擬內存空間中, 0000000000000000 - 00007fffffffffff(128TB)爲用戶空間,ffff800000000000 - ffffffffffffffff(128TB)爲內核空間。目前經常使用的分配設計:
Virtual memory map with 4 level page tables: 0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm hole caused by [47:63] sign extension ffff800000000000 - ffff87ffffffffff (=43 bits) guard hole, reserved for hypervisor ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB) ... unused hole ... ffffec0000000000 - fffffbffffffffff (=44 bits) kasan shadow memory (16TB) ... unused hole ... vaddr_end for KASLR fffffe0000000000 - fffffe7fffffffff (=39 bits) cpu_entry_area mapping fffffe8000000000 - fffffeffffffffff (=39 bits) LDT remap for PTI ffffff0000000000 - ffffff7fffffffff (=39 bits) %esp fixup stacks ... unused hole ... ffffffef00000000 - fffffffeffffffff (=64 GB) EFI region mapping space ... unused hole ... ffffffff80000000 - ffffffff9fffffff (=512 MB) kernel text mapping, from phys 0 ffffffffa0000000 - fffffffffeffffff (1520 MB) module mapping space [fixmap start] - ffffffffff5fffff kernel-internal fixmap range ffffffffff600000 - ffffffffff600fff (=4 kB) legacy vsyscall ABI ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole
from http://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt
剩下的是用戶內存空間:
MMAP_THRESHOLD
設置的字節數,它的缺省值是 128 kB,能夠經過 mallopt()
去調整這個設置值。還能夠用於進程間通訊IPC(共享內存)。2 爲了防止內存被攻擊,好比棧溢出攻擊和堆溢出攻擊等,Linux在特定段之間使用隨機偏移,使段的起始地址是隨機值。
Linux 系統上的 ASLR 等級能夠經過文件 /proc/sys/kernel/randomize_va_space 來進行設置,它支持如下取值:
3 每一個段都有特定的安全控制(權限):
vm_flags |
第三列,如r-xp |
此段虛擬地址空間的屬性。每種屬性用一個字段表示,r表示可讀,w表示可寫,x表示可執行,p和s共用一個字段,互斥關係,p表示私有段,s表示共享段,若是沒有相應權限,則用’-’代替 |
from https://blog.csdn.net/lijzheng/article/details/23618365
4 Linux虛擬內存是按頁分配,每頁大小爲4KB或者2M(大頁內存),默認是4K
1 #include<iostream>
2 #include <unistd.h>
3 using namespace std;
4 //long a[1024*1024] = {0};
5 int main()
6 {
7 void *heap;
8 int *x = new int[1024]();
9 cout << hex <<"x: " << x <<endl;
10 heap = sbrk(0);
11 //cout << hex << "a:" << (long) &a <<endl;
12 cout << hex << "heap: " << (long) heap <<endl;
13 cout << hex << "heap: " << (long)heap - (long)x <<endl;
14 while(1);
15 return 0;
16 }
g++ -g -std=c++11 -o main mem.cpp
./main
關閉了內存地址隨機化
pmap -X 8117
8117: ./main
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Swap Locked Mapping
00400000 r-xp 00000000 08:11 43014235 4 4 4 4 0 0 0 main
00601000 r--p 00001000 08:11 43014235 4 4 4 4 4 0 0 main
00602000 rw-p 00002000 08:11 43014235 4 4 4 4 4 0 0 main
//程序的text段,只讀數據段,和全局/靜態數據段;
00603000 rw-p 00000000 00:00 0 136 8 8 8 8 0 0 [heap]
//程序的堆內存段;
7ffff71e2000 r-xp 00000000 08:11 266401 88 88 18 88 0 0 0 libgcc_s.so.1
7ffff71f8000 ---p 00016000 08:11 266401 2044 0 0 0 0 0 0 libgcc_s.so.1
7ffff73f7000 rw-p 00015000 08:11 266401 4 4 4 4 4 0 0 libgcc_s.so.1
7ffff73f8000 r-xp 00000000 08:11 266431 1052 224 3 224 0 0 0 libm-2.21.so
7ffff74ff000 ---p 00107000 08:11 266431 2044 0 0 0 0 0 0 libm-2.21.so
7ffff76fe000 r--p 00106000 08:11 266431 4 4 4 4 4 0 0 libm-2.21.so
7ffff76ff000 rw-p 00107000 08:11 266431 4 4 4 4 4 0 0 libm-2.21.so
7ffff7700000 r-xp 00000000 08:11 266372 1792 1152 8 1152 0 0 0 libc-2.21.so
7ffff78c0000 ---p 001c0000 08:11 266372 2048 0 0 0 0 0 0 libc-2.21.so
7ffff7ac0000 r--p 001c0000 08:11 266372 16 16 16 16 16 0 0 libc-2.21.so
7ffff7ac4000 rw-p 001c4000 08:11 266372 8 8 8 8 8 0 0 libc-2.21.so
7ffff7ac6000 rw-p 00000000 00:00 0 16 12 12 12 12 0 0
7ffff7aca000 r-xp 00000000 08:11 46146360 960 856 283 856 0 0 0 libstdc++.so.6.0.20
7ffff7bba000 ---p 000f0000 08:11 46146360 2048 0 0 0 0 0 0 libstdc++.so.6.0.20
7ffff7dba000 r--p 000f0000 08:11 46146360 32 32 32 32 32 0 0 libstdc++.so.6.0.20
7ffff7dc2000 rw-p 000f8000 08:11 46146360 8 8 8 8 8 0 0 libstdc++.so.6.0.20
7ffff7dc4000 rw-p 00000000 00:00 0 84 16 16 16 16 0 0
7ffff7dd9000 r-xp 00000000 08:11 266344 144 144 1 144 0 0 0 ld-2.21.so
//程序的內存映射區,主要是動態庫加載到該內存區,包括動態庫的text代碼段和數據data段。
//中間沒有名字的,屬於程序的匿名映射段,主要提供大內存分配。
7ffff7fd4000 rw-p 00000000 00:00 0 20 20 20 20 20 0 0
7ffff7ff5000 rw-p 00000000 00:00 0 12 12 12 12 12 0 0
7ffff7ff8000 r--p 00000000 00:00 0 8 0 0 0 0 0 0 [vvar]
7ffff7ffa000 r-xp 00000000 00:00 0 8 4 0 4 0 0 0 [vdso]
//vvar page,kernel的一些系統調用的數據會映射到這個頁面,用戶能夠直接在用戶空間訪問;
//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,就是內核實現了glibc的一些系統調用,而後能夠直接在用戶空間執行,提升系統調用效率和減小與glibc的耦合。
from https://lwn.net/Articles/615809/
7ffff7ffc000 r--p 00023000 08:11 266344 4 4 4 4 4 0 0 ld-2.21.so
7ffff7ffd000 rw-p 00024000 08:11 266344 4 4 4 4 4 0 0 ld-2.21.so
7ffff7ffe000 rw-p 00000000 00:00 0 4 4 4 4 4 0 0
7ffffffde000 rw-p 00000000 00:00 0 136 8 8 8 8 0 0 [stack]
//此段爲程序的棧區
ffffffffff600000 r-xp 00000000 00:00 0 4 0 0 0 0 0 0 [vsyscall]
//此段是Linux實現vsyscall系統調用vsyscall庫代碼段
===== ==== === ========== ========= ==== ======
12744 2644 489 2644 172 0 0 KB
思考問題:
1 棧爲何要由高地址向低地址擴展,堆爲何由低地址向高地址擴展?
2 如何查看進程虛擬地址空間的使用狀況?
3 對比堆和棧優缺點?
參考答案:
from http://www.javashuo.com/article/p-gsrstegm-hs.html
from https://zhuanlan.zhihu.com/p/25816426
此時
在AT&T中:
以上兩條指令能夠被leave指令取代
由上面棧內存佈局能夠看出,棧很容易被破壞和攻擊,經過棧緩衝器溢出攻擊,用攻擊代碼首地址來替換函數幀的返回地址,當子函數返回時,便跳轉到攻擊代碼處執行,獲取系統的控制權,因此操做系統和編譯器採用了一些經常使用的防攻擊的方法:
ASLR(地址空間佈局隨機化): 操做系統能夠將函數調用棧的起始地址設爲隨機化(這種技術被稱爲內存佈局隨機化,即Address Space Layout Randomization (ASLR) ),加大了查找函數地址及返回地址的難度。
Cannary
gcc關於棧溢出檢測的幾個參數
·
from http://walkerdu.com/2017/04/21/gcc-stack-overflow-check/
開啓Canary以後,函數開始時在ebp和臨時變量之間插入一個隨機值,函數結束時驗證這個值。若是不相等(也就是這個值被其餘值覆蓋了),就會調用 _stackchk_fail函數,終止進程。對應GCC編譯選項-fno-stack-protector
解除該保護。
NX.
開啓NX保護以後,程序的堆棧將會不可執行。對應GCC編譯選項-z execstack
解除該保護。
4 棧異常處理
思考問題:
1 遞歸調用函數怎麼從20層直接返回到17層,程序能夠正常運行?
2 調用約定有哪些?
參考答案:
1
怎麼獲得17層rbp的值, 就是經過反覆取rbp的值(rbp保持了上一幀的rbp);核心代碼以下:
4 /*change stack*/ 5 int ret_stack(int layer) 6 { 7 unsigned long rbp = 0; 8 unsigned long layer_rbp = 0; 9 int depth = 0; 10 11 /* 1.獲得首層函數的棧基址 */ 12 __asm__ volatile( 13 "movq %%rbp, %0 \n\t" 14 :"=r"(rbp) 15 : 16 :"memory"); 17 18 layer_rbp = rbp; 19 cout << hex<< rbp <<endl; 20 /* 2.逐層回溯棧基址 */ 21 for(; (depth < layer) && (0 != layer_rbp) && (0 != *(unsigned long *)layer_rbp) && (layer_rbp != *(unsigned long *)layer_rbp); ++depth) { 22 cout << hex<< layer_rbp <<endl; 23 layer_rbp = *(unsigned long *)layer_rbp; 24 } 25 cout << hex<< layer_rbp <<endl; 26 //change current rbp to target layer rbp 27 unsigned long *x = (unsigned long *)rbp; 28 *x = layer_rbp; 29 cout << hex<< x << " v:" << *x <<endl; 30 return depth; 31 }
2
在這些調用約定中,咱們最經常使用是如下幾種約定
1. cdecl
2. stdcall
3. thiscall
cdecl 是c/c++默認的調用約定。
stdcall 它是微軟Win32 API的一準標準,咱們經常使用的回調函數就是經過這種調用方式
thiscall 是c++中非靜態類成員函數的默認調用約定
from https://zhuanlan.zhihu.com/p/35983838
1. new操做符作兩件事,分配內存+調用構造函數初始化。你不能改變它的行爲;
2. delete操做符一樣作兩件事,調用析構函數+釋放內存。你不能改變它的行爲;
operator new :
The default allocation and deallocation functions are special components of the standard library; They have the following unique properties:
operator new
are declared in the global namespace, not within thestd namespace.<new>
is included or not. Ifset_new_handler has been used to define anew_handler function, this new-handler function is called by the default definitions of the allocating versions ((1) and (2)) if they fail to allocate the requested storage.
operator new
can be called explicitly as a regular function, but in C++, new
is an operator with a very specific behavior: An expression with the new
operator, first calls function operator 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.
from http://www.cplusplus.com/reference/new/operator%20new/
1. 是用來專門分配內存的函數,爲new操做符調用,你能增長額外的參數重載函數operator new(有限制):
限制1: 第一個參數類型必須是size_t;
限制2: 函數必須返回void*;
2. operator new 底層通常調用malloc函數(gcc+glibc)分配內存;
3. operator new 分配失敗會拋異常(默認),經過傳遞參數也能夠不拋異常,返回空指針;
operator delete :
1. 是用來專門分配內存的函數,爲delete操做符調用,你能增長額外的參數重載函數operator delete(有限制):
限制1: 第一個參數類型必須是void*;
限制2: 函數必須返回void;
2. operator delete底層通常調用free函數(gcc+glibc)釋放內存;
3. operator delete分配失敗會拋異常(默認),經過傳遞參數也能夠不拋異常,返回空指針;
1. placement new 其實就是new的一種重載,placement new是一種特殊的operator new,做用於一塊已分配但未處理或未初始化的raw內存,就是用一塊已經分配好的內存上重建對象(調用構造函數);
2. 它是C++庫標準的一部分;
3. placement delete 什麼都不作;
1. 對應會調用operator new[]/delete[]函數;
2. 按對象的個數,分別調用構造函數和析構函數;
from http://www.cplusplus.com/reference/new/operator%20new[]/
class-specific allocation functions
|
||
void* T::operator new ( std::size_t count );
|
(15) | |
void* T::operator new[]( std::size_t count );
|
(16) | |
void* T::operator new ( std::size_t count, std::align_val_t al );
|
(17) | (since C++17) |
void* T::operator new[]( std::size_t count, std::align_val_t al );
|
(18) | (since C++17) |
class-specific placement allocation functions
|
||
void* T::operator new ( std::size_t count, user-defined-args... );
|
(19) | |
void* T::operator new[]( std::size_t count, user-defined-args... );
|
(20) | |
void* T::operator new ( std::size_t count, std::align_val_t al, user-defined-args... ); |
(21) | (since C++17) |
void* T::operator new[]( std::size_t count, std::align_val_t al, user-defined-args... ); |
(22) | (since C++17) |
from http://en.cppreference.com/w/cpp/memory/new/operator_new
定製對象特殊new/delete函數;
實現通常是使用全局:
::operator new
::operator delete
關鍵點:
Ifset_new_handlerhas been used to define anew_handlerfunction, this new-handler function is called by the default definitions of the allocating versions ((1) and (2)) if they fail to allocate the requested storage.
The other signatures ((2) and (3)) are never called by a delete-expression (the
delete
operator always calls the ordinary version of this function, and exactly once for each of its arguments). These other signatures are only called automatically by a new-expression when their object construction fails (e.g., if the constructor of an object throws while being constructed by a new-expression withnothrow, the matchingoperator deletefunction accepting anothrowargument is called).
1 malloc和free是怎麼實現的?
2 malloc 分配多大的內存,就佔用多大的物理內存空間嗎?
3 free 的內存真的釋放了嗎(還給 OS ) ?
4 既然堆內內存不能直接釋放,爲何不所有使用 mmap 來分配?
5 如何查看堆內內存的碎片狀況?
6 除了 glibc 的 malloc/free ,還有其餘第三方實現嗎?
參考答案:
對象生命週期的管理,懸掛指針(dangling pointer)/空指針等問題;
在對象構造的時候分配內存,在對象做用域以外釋放內存,幫助程序員管理動態內存;
shared_ptr 是引用計數型(reference counting)智能指針, shared_ptr包含兩個成員,一個是指向真正數據的指針,另外一個是引用計數ref_count模塊指針,對比GCC實現,大體原理以下,
from http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html
共享對象(數據)(賦值拷貝),引用計數加1,指針消亡,引用計數減1,當引用計數爲0,自動析構所指的對象,引用計數是線程安全的(原子操做)。
shared_ptr關鍵點:
1. 提升效率,內存分配一次搞定(對象數據和ref_count控制模塊);
2. 防止異常致使內存泄漏,參考https://herbsutter.com/gotw/_102/;
3. 因爲一次性分配內存,對象數據和ref_count控制模塊生命週期被綁定在一塊兒,須要等到全部的weak引用爲0時才能最終釋放內存(delete),當use_count爲0時只會調用析構函數;//from http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared,因此對象內存的佔用時間比較長;
4. 用enable_shared_from_this來使一個類能獲取自身的shared_ptr;
5. 不能在對象的構造函數中使用shared_from_this()函數,爲何
由於對象尚未構造完畢,share_ptr尚未初始化構造徹底。 構造順序:先須要調用enable_shared_from_this類的構造函數,接着調用對象的構造函數,最後須要調用shared_ptr類的構造函數初始化enable_shared_from_this的成員變量weak_this_。而後才能使用shared_from_this()函數。
6. 大量的shared_ptr會致使程序性能降低(相對其餘指針)。
獨佔指針,不共享,不能賦值拷貝;
unique
_ptr關鍵點:
1. 若是對象不須要共享,通常最好都用unique_ptr,性能好,更安全;
2. 能夠經過move語義傳遞對象的生命週期控制權;
3. 函數能夠返回unique_ptr對象,爲何?
RVO和NRVO
當函數返回一個對象時,理論上會產生臨時變量,那必然是會致使新對象的構造和舊對象的析構,這對效率是有影響的。C++編譯針對這種狀況容許進行優化,哪怕是構造函數有反作用,這叫作返回值優化(RVO),返回有名字的對象叫作具名返回值優化(NRVO),就那RVO來講吧,原本是在返回時要生成臨時對象的,如今構造返回對象時直接在接受返回對象的空間中構造了。假設不進行返回值優化,那麼上面返回unique_ptr會不會有問題呢?也不會。由於標準容許編譯器這麼作:
1.若是支持move構造,那麼調用move構造。
2.若是不支持move,那就調用copy構造。
3.若是不支持copy,那就報錯吧。
顯然的,unique_ptr是支持move構造的,unique_ptr對象能夠被函數返回。
from https://blog.csdn.net/booirror/article/details/44455293
思考問題:
1 C++的賦值和Java的有什麼區別?
C++的賦值能夠是對象拷貝也能夠對象引用,java的賦值是對象引用;
2 smart_ptr有哪些坑能夠仍然致使內存泄漏?
1. shared_ptr初始化構造函數指針,通常是能夠動態管理的內存地址,若是不是就可能致使內存泄漏;
2. shared_ptr要求內部new和delete實現必須是成對,一致性,若是不是就可能致使內存泄漏;
3. shared_ptr對象和其餘大多數STL容器同樣,自己不是線程安全的,須要用戶去保證;
3 unique_ptr有哪些限制?
1. 只能移動賦值轉移數據,不能拷貝;
2. 不支持類型轉換(cast);
4 智能指針是異常安全的嗎?
所謂異常安全是指,當異常拋出時,帶有異常安全的函數會:
1.不泄露任何資源
2.不容許數據被破壞
智能指針就是採用RAII技術,即以對象管理資源來防止資源泄漏。
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 (assuming T meets the common requirements) is std::bad_alloc, and that is thrown only by functions which are explicitly documented as possibly throwing std::bad_alloc.
from https://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/smart_ptr.htm
5 智能指針是線程安全的嗎?
智能指針對象的引用計數模塊是線程安全的,由於 shared_ptr 有兩個數據成員,讀寫操做不能原子化,因此對象自己不是線程安全的,須要用戶去保證線程安全。
Thread Safety
shared_ptr
objects offer the same level of thread safety as built-in types. Ashared_ptr
instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Differentshared_ptr
instances 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.
from ttps://www.boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_thread_safety
C++11 提供最小垃圾支持:
declare_reachable undeclare_reachable declare_no_pointers undeclare_no_pointers pointer_safety get_pointer_safety
受限不少,不多使用
參考 http://www.stroustrup.com/C++11FAQ.html#gc-abi
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2585.pdf
參考問題:
1 C++能夠經過哪些技術來支持「垃圾回收」?
smart_ptr,RAII, move語義等;
2 RAII是指什麼?
RAII是指Resource Acquisition Is Initialization的設計模式,
RAII要求,資源的有效期與持有資源的對象的生命期嚴格綁定,即由對象的構造函數完成資源的分配(獲取),同時由析構函數完成資源的釋放。在這種要求下,只要對象能正確地析構,就不會出現資源泄露問題。
當一個函數須要經過多個局部變量來管理資源時,RAII就顯得很是好用。由於只有被構形成功(構造函數沒有拋出異常)的對象纔會在返回時調用析構函數[4],同時析構函數的調用順序剛好是它們構造順序的反序[5],這樣既能夠保證多個資源(對象)的正確釋放,又能知足多個資源之間的依賴關係。
因爲RAII能夠極大地簡化資源管理,並有效地保證程序的正確和代碼的簡潔,因此一般會強烈建議在C++中使用它。
STL(C++標準模板庫)引入的一個Allocator概念。整個STL全部組件的內存均從allocator分配。也就是說,STL並不推薦使用 new/delete 進行內存管理,而是推薦使用allocator。
SGI STL allocator設計:
對象的構造和析構採用placement new函數
from https://zcheng.ren/2016/08/16/STLAllocater/#%E7%AC%AC%E4%BA%8C%E7%BA%A7%E9%85%8D%E7%BD%AE%E5%99%A8
4。
思考問題:
1. vector內存設計和array的區別和適用的場景?
2. 遍歷map與遍歷vector哪一個更快,爲何?
3. STL的map和unordered_map內存設計各有什麼不一樣?
因爲C++語言對內存有主動控制權,內存使用靈活和效率高,但代價是不當心使用就會致使如下內存錯誤:
• memory overrun:寫內存越界
• double free:同一塊內存釋放兩次
• use after free:內存釋放後使用
• wild free:釋放內存的參數爲非法值
• access uninitialized memory:訪問未初始化內存
• read invalid memory:讀取非法內存,本質上也屬於內存越界
• memory leak:內存泄露
• use after return:caller訪問一個指針,該指針指向callee的棧內內存
• stack overflow:棧溢出
經常使用的解決內存錯誤的方法
靜態代碼檢測是指無需運行被測代碼,經過詞法分析、語法分析、控制流、數據流分析等技術對程序代碼進行掃描,找出代碼隱藏的錯誤和缺陷,如參數不匹配,有歧義的嵌套語句,錯誤的遞歸,非法計算,可能出現的空指針引用等等。統計證實,在整個軟件開發生命週期中,30%至70%的代碼邏輯設計和編碼缺陷是能夠經過靜態代碼分析來發現和修復的。在C++項目開發過程當中,由於其爲編譯執行語言,語言規則要求較高,開發團隊每每要花費大量的時間和精力發現並修改代碼缺陷。因此C++靜態代碼分析工具可以幫助開發人員快速、有效的定位代碼缺陷並及時糾正這些問題,從而極大地提升軟件可靠性並節省開發成本。
靜態代碼分析工具的優點:
一、自動執行靜態代碼分析,快速定位代碼隱藏錯誤和缺陷。
二、幫助代碼設計人員更專一於分析和解決代碼設計缺陷。
三、減小在代碼人工檢查上花費的時間,提升軟件可靠性並節省開發成本。
一些主流的靜態代碼檢測工具:
免費的cppcheck,clang static analyzer;商用的coverity,pclint等
各個工具性能對比: http://www.51testing.com/html/19/n-3709719.html
所謂的代碼動態檢測,就是須要再程序運行狀況下,經過插入特殊指令,進行動態檢測和收集運行數據信息,而後分析給出報告。
1. 爲了檢測內存非法使用,須要hook內存分配和操做函數。hook的方法能夠是用C-preprocessor,也能夠是在連接庫中直接定義(由於Glibc中的malloc/free等函數都是weak symbol),或是用LD_PRELOAD。另外,經過hook strcpy(),memmove()等函數能夠檢測它們是否引發buffer overflow。
2. 爲了檢查內存的非法訪問,須要對程序的內存進行bookkeeping,而後截獲每次訪存操做並檢測是否合法。bookkeeping的方法大同小異,主要思想是用shadow memory來驗證某塊內存的合法性。至於instrumentation的方法各類各樣。有run-time的,好比經過把程序運行在虛擬機中或是經過binary translator來運行;或是compile-time的,在編譯時就在訪存指令時就加入檢查操做。另外也能夠經過在分配內存先後加設爲不可訪問的guard page,這樣能夠利用硬件(MMU)來觸發SIGSEGV,從而提升速度。
3. 爲了檢測棧的問題,通常在stack上設置canary,即在函數調用時在棧上寫magic number或是隨機值,而後在函數返回時檢查是否被改寫。另外能夠經過mprotect()在stack的頂端設置guard page,這樣棧溢出會致使SIGSEGV而不至於破壞數據。
AddressSanitizer | Valgrind/Memcheck | Dr. Memory | Mudflap | Guard Page | gperftools | ||
---|---|---|---|---|---|---|---|
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 (see AddressSanitizerUseAfterReturn) | no | no | no | no | no | |
UMR | no (see MemorySanitizer) | yes | yes | ? | no | no | |
Leaks | yes (see LeakSanitizer) | yes | yes | ? | no | yes |
DBI: 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.
mudflap was removed in GCC 4.9, as it has been superseded by AddressSanitizer.
Guard Page: a family of memory error detectors (Electric fence or DUMA on 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 allocator provides both guard pages and canary values for more precise detection of OOB writes, so it's better than guard page-only detectors.
from https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools
固然應用程序也能夠直接使用系統調用從內核分配內存,本身根據程序特性來維護內存,可是會大大增長開發成本。
from https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/
brk()/sbrk() // 經過移動Heap堆頂指針brk,達到增長內存目的 mmap()/munmap() // 經過文件影射的方式,把文件映射到mmap區
- 分配內存 <
DEFAULT_MMAP_THRESHOLD
,走brk,從內存池獲取,失敗的話走brk系統調用- 分配內存 >
DEFAULT_MMAP_THRESHOLD
,走mmap,直接調用mmap系統調用其中,
DEFAULT_MMAP_THRESHOLD
默認爲128k,可經過mallopt
進行設置。
sbrk/brk系統調用的實現:分配內存是經過調節堆頂的位置來實現, 堆頂的位置是經過函數 brk 和 sbrk 進行動態調整,參考例子:
(1) 初始狀態:如圖 (1) 所示,系統已分配 ABCD 四塊內存,其中 ABD 在堆內分配, C 使用 mmap 分配。爲簡單起見,圖中忽略瞭如共享庫等文件映射區域的地址空間。
(2) E=malloc(100k) :分配 100k 內存,小於 128k ,從堆內分配,堆內剩餘空間不足,擴展堆頂 (brk) 指針。
(3) free(A) :釋放 A 的內存,在 glibc 中,僅僅是標記爲可用,造成一個內存空洞 ( 碎片 ),並無真正釋放。若是此時須要分配 40k 之內的空間,可重用此空間,剩餘空間造成新的小碎片。
(4) free(C) :C 空間大於 128K ,使用 mmap 分配,若是釋放 C ,會調用 munmap 系統調用來釋放,並會真正釋放該空間,還給 OS ,如圖 (4) 所示。
from http://tencentdba.com/blog/linux-virtual-memory-glibc/
因此free的內存不必定真正的歸還給OS,隨着系統頻繁地 malloc 和 free ,尤爲對於小塊內存,堆內將產生愈來愈多不可用的碎片,致使「內存泄露」。而這種「泄露」現象使用 valgrind 是沒法檢測出來的。
from http://tencentdba.com/blog/linux-virtual-memory-glibc/
內存池方案一般一次從系統申請一大塊內存塊,而後基於在這塊內存塊能夠進行不一樣內存策略實現,
能夠比較好得解決上面提到的問題,通常採用內存池有如下好處:
1.少許系統申請次數,很是少(幾沒有) 堆碎片。
2.因爲沒有系統調用等,比一般的內存申請/釋放(好比經過malloc, new等)的方式快。
3.能夠檢查應用的任何一塊內存是否在內存池裏。
4.寫一個」堆轉儲(Heap-Dump)」到你的硬盤(對過後的調試很是有用)。
5.能夠更方便實現某種內存泄漏檢測(memory-leak detection)。
6.減小額外系統內存管理開銷,能夠節約內存;
各個內存分配器的實現都是在以上的各類指標中進行權衡選擇.
主要核心技術點:
等等
主要核心技術點:
from http://gao-xiao-long.github.io/2017/11/25/tcmalloc/
TCMalloc給每一個線程分配了一個線程局部緩存。小分配能夠直接由線程局部緩存來知足。須要的話,會將對象從中央數據結構移動到線程局部緩存中,同時按期的垃圾收集將用於把內存從線程局部緩存遷移回中央數據結構中:
2. Thread Specific Free List/size-classes [8,16,32,…32k]: 更好小對象內存分配;
每一個小對象的大小都會被映射到170個可分配的尺寸類別中的一個。例如,在分配961到1024字節時,都會歸整爲1024字節。尺寸類別這樣隔開:較小的尺寸相差8字節,較大的尺寸相差16字節,再大一點的尺寸差32字節,如此類推。最大的間隔(對於尺寸 >= ~2K的)是256字節。
一個線程緩存對每一個尺寸類都包含了一個自由對象的單向鏈表
3. The central page heap:更好的大對象內存分配
一個大對象的尺寸(> 32K)會被除以一個頁面尺寸(4K)並取整(大於結果的最小整數),同時是由中央頁面堆來處理 的。中央頁面堆又是一個自由列表的陣列。對於i < 256
而言,第k
個條目是一個由k
個頁面組成的自由列表。第256
個條目則是一個包含了長度>= 256
個頁面的自由列表:
4. Spans:
TCMalloc管理的堆由一系列頁面組成。連續的頁面由一個「跨度」(Span
)對象來表示。一個跨度能夠是已被分配或者是自由的。若是是自由的,跨度則會是一個頁面堆鏈表中的一個條目。若是已被分配,它會是一個已經被傳遞給應用程序的大對象,或者是一個已經被分割成一系列小對象的一個頁面。若是是被分割成小對象的,對象的尺寸類別會被記錄在跨度中。
由頁面號索引的中央數組能夠用於找到某個頁面所屬的跨度。例如,下面的跨度a佔據了2個頁面,跨度b佔據了1個頁面,跨度c佔據了5個頁面最後跨度d佔據了3個頁面。
from http://gao-xiao-long.github.io/2017/11/25/tcmalloc/
jemalloc 是FreeBSD的提供的內存分配管理模塊
主要核心技術點:
1. 與tcmalloc相似,每一個線程一樣在<32KB的時候無鎖使用線程本地cache;
2. Jemalloc在64bits系統上使用下面的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對象經過全局紅黑樹在對數時間內查找
4. 虛擬內存被邏輯上分割成chunks(默認是4MB,1024個4k頁),應用線程經過round-robin算法在第一次malloc的時候分配arena, 每一個arena都是相互獨立的,維護本身的chunks, chunk切割pages到small/large對象。free()的內存老是返回到所屬的arena中,而無論是哪一個線程調用free().
上圖能夠看到每一個arena管理的arena chunk結構, 開始的header主要是維護了一個page map(1024個頁面關聯的對象狀態), header下方就是它的頁面空間。 Small對象被分到一塊兒, metadata信息存放在起始位置。 large chunk相互獨立,它的metadata信息存放在chunk header map中。
5. 經過arena分配的時候須要對arena bin(每一個small size-class一個,細粒度)加鎖,或arena自己加鎖。
而且線程cache對象也會經過垃圾回收指數退讓算法返回到arena中。
一些主流的內存分配方案性能比較:
總結:
能夠看出tcmalloc和jemalloc性能接近,比ptmalloc性能要好,在多線程環境使用tcmalloc和jemalloc效果很是明顯。
通常支持多核多線程擴展狀況下可使用jemalloc;反之使用tcmalloc多是更好的選擇。
能夠參考:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
http://www.javashuo.com/article/p-fyeutdio-bk.html
思考問題:
1 jemalloc和tcmalloc最佳實踐是什麼?
2 心裏池的設計有哪些套路?爲何?
參考答案:
經過讀取/proc/$PID/maps 和 smaps 的數據,解析數據,生成進程的虛列內存映像和一些內存統計:
pmap -X -p 31931
31931: ./bug_tc
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Swap Locked Mapping
…
7f37e4c36000 rw-p 00000000 00:00 0 132 88 88 80 88 44 0 [heap]
7fffff85c000 rw-p 00000000 00:00 0 7824 7820 7820 7820 7820 0 0 [stack]
…
===== ===== ===== ========== ========= ==== ======
71396 16540 13902 16540 13048 0 0 KB
裏面能夠查看程序堆和棧內存大小區間,程序所佔內存大小,主要是關注PSS
如下內存統計名稱解釋:
VSS:Virtual Set Size,虛擬內存耗用內存,包括共享庫的內存
RSS:Resident Set Size,實際使用物理內存,包括共享庫
PSS:Proportional Set Size,實際使用的物理內存,共享庫按比例分配
USS:Unique Set Size,進程獨佔的物理內存,不計算共享庫,也能夠理解爲將進程殺死能釋放出的內存
通常VSS >= RSS >= PSS >= USS。
通常統計程序的內存佔用,PSS是最好的選擇,比較合理。
詳細請參考:
man 手冊
http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/
思考問題:
1 各個工具優缺點和使用場景?
2 linux內存統計裏面,劃分了哪些統計?
參加答案:
valgrind massif 採集完數據生成數據文件,數據文件會顯示每一幀的程序使用的堆內存大小:
MB 3.952^ # | @#: | :@@#: | @@::::@@#: | @ :: :@@#:: | @@@ :: :@@#:: | @@:@@@ :: :@@#:: | :::@ :@@@ :: :@@#:: | : :@ :@@@ :: :@@#:: | :@: :@ :@@@ :: :@@#:: | @@:@: :@ :@@@ :: :@@#::: | : :: ::@@:@: :@ :@@@ :: :@@#::: | :@@: ::::: ::::@@@:::@@:@: :@ :@@@ :: :@@#::: | ::::@@: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | @: ::@@: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | @: ::@@: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | @: ::@@:::::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | ::@@@: ::@@:: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | :::::@ @: ::@@:: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: | @@:::::@ @: ::@@:: ::: ::::::: @ :::@@:@: :@ :@@@ :: :@@#::: 0 +----------------------------------------------------------------------->Mi 0 626.4 Number of snapshots: 63 Detailed snapshots: [3, 4, 10, 11, 15, 16, 29, 33, 34, 36, 39, 41, 42, 43, 44, 49, 50, 51, 53, 55, 56, 57 (peak)]
-------------------------------------------------------------------------------- n time(B) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 10 10,080 10,080 10,000 80 0 11 12,088 12,088 12,000 88 0 12 16,096 16,096 16,000 96 0 13 20,104 20,104 20,000 104 0 14 20,104 20,104 20,000 104 0 99.48% (20,000B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->49.74% (10,000B) 0x804841A: main (example.c:20) | ->39.79% (8,000B) 0x80483C2: g (example.c:5) | ->19.90% (4,000B) 0x80483E2: f (example.c:11) | | ->19.90% (4,000B) 0x8048431: main (example.c:23) | | | ->19.90% (4,000B) 0x8048436: main (example.c:25) | ->09.95% (2,000B) 0x80483DA: f (example.c:10) ->09.95% (2,000B) 0x8048431: main (example.c:23)
更多細節參考 http://valgrind.org/docs/manual/ms-manual.html
gperftools 工具裏面的內存監控器,統計監控程序使用內存的多少,能夠查看內存使用熱點,
默認是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::CentralFreeList::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::CentralFreeList::FetchFromOneSpans
2 5.3% 71.1% 2 5.3% tcmalloc::ThreadCache::GetThreadHeap (inline)
2 5.3% 76.3% 2 5.3% tcmalloc::ThreadCache::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::CentralFreeList::ReleaseListToSpans
1 2.6% 89.5% 6 15.8% tcmalloc::CentralFreeList::RemoveRange
1 2.6% 92.1% 1 2.6% tcmalloc::SizeMap::GetSizeClass (inline)
第一列表明這個函數調用自己直接使用了多少內存,
第二列表示第一列的百分比,
第三列是從第一行到當前行的全部第二列之和,
第四列表示這個函數調用本身直接使用加上全部子調用使用的內存總和,
第五列是第四列的百分比。
基本上只要知道這些,就能很好的掌握每一時刻程序運行內存使用狀況了,而且對比不一樣時段的不一樣profile數據,能夠分析出內存走向,進而定位熱點和泄漏。
Kcachegrind模式:利用pprof生成callgrind格式的文件便可,KCachegrind的GUI工具,用於分析callgrind
:
更多細節參考 https://github.com/gperftools/gperftools/blob/master/docs/heapprofile.html
windows 版本:
https://sourceforge.net/projects/precompiledbin/files/latest/download?source=files
思考問題:
1 說一說內存對設備(手機,PC,嵌入式設備)性能影響?
參考答案:
參考:
http://www.javashuo.com/article/p-gsrstegm-hs.html
https://blog.csdn.net/buxizhizhou530/article/details/46695999
http://www.cnblogs.com/heleifz/p/shared-principle-application.html
https://herbsutter.com/gotw/_102/
https://lanzkron.wordpress.com/2012/04/22/make_shared-almost-a-silver-bullet/
http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared