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