0%

认识C++内存管理的工具 (一)

1. 内存管理函数的层次

从下图中可以看到,C++程序员处于的位置是最上层的Applications,用的最多的是newnew[],如果用容器,则内存基本不用管理。其次也可以调用malloc。至于最底层的操作系统级别的API,没有可移植性。它们之间的调用关系如上图的箭头所示。

2. Memory primitives分类

分配 释放 所属 可否重载
malloc free C函数
new delete C++表达式
::operator new() ::operator delete() C++函数
allocator<T>::allocate() allocator<T>::deallocate() STL分配器 可以自己设计搭配容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void primitives_sample()
{
// CRT 运行时函数
void *p1 = malloc( 512 );
free( p1 );

// new
complex<int> *p2 = new complex<int>;
delete p2;

// ::operator new() 全局函数, 可被重载
void *p3 = ::operator new( sizeof( int ) ); // 内部调用 malloc
::operator delete( p3 ); //内部调用delete

// allocator 分配器, STL 容器分配内存的方式
void *p4 = allocator<int>().allocate( 7 );
allocator<int>().deallocate( (int *)p4, 7 );
}

3. new的初步探究

c++的程序员基本都会用new来为对象分配一个堆内存,并且new会调用对应的构造函数,构造函数是用来初始化对象的,所以总结出new的功能是:

  1. 在堆中分配一块指定对象大小的内存
  2. 将返回的指针转换为指向对象类型的指针
  3. 通过指针调用对象相应的构造函数

image-20210315204443125

4. 测试new的调用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
struct A
{
int a;
int b;
A() {}
A(int _a, int _b): a(_a), b(_b) {}
};

int main()
{
A* tmp_a = new A(2, 8);
delete tmp_a;
}

上面的4张图是在MSVC中反汇编的运行时代码。从第1张图可以看出,new调用了operator new,从第3张图可以看出,operator new内部调用了malloc。实际上编译器是在new的地方调用了对应的构造函数,并不是在new的内部,new只是编译器识别的一个标识符,并不是函数,编译器看到new后会malloc,然后调用构造函数。

VS2019可以看到operator new的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}

if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}

上面的operator new的作用是调用malloc分配内存。当malloc成功后直接返回。当malloc失败后,并不会再次malloc,而是调用_callnewh() new_handler(),这个函数的作用是向自己定义的函数索取内存,所以new_handler可以理解为释放一些缓存,调用完new_handler后,可能释放了内存,这个时候再尝试调用malloc获取内存。

5. delete的初步探究

image-20210315210220075

6. 测试delete的调用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace std;

struct A
{
int a;
int b;
A() {}
A(int _a, int _b) : a(_a), b(_b) {}
~A() {}
};

int main()
{
A* tmp_a = new A(2, 8);
delete tmp_a;
}