内存分配的每个层面
C++的内存管理方式有如下四种,一般不直接使用操作系统的API进行管理,以尽可能避免与特定操作系统绑定。

四种内存操作方法的特性如下:

new/detele expression
内存申请
通过new进行内存申请的过程:
- 通过operator new()操作分配一个目标类型大小的内存,图中为Complex的大小。
- 通过static_cast将得到的内存块强制转化为目标类型指针,这里是Complex*。
- 调用目标类型的构造方法(注意:直接通过pc->Complex::Complex(1,2)这样的方法调用构造函数只有编译器可以做,用户这样做将产生错误)。
operator new()操作的内部调用了malloc()函数

内存释放
通过delete进行内存释放的过程:
- 调用对象的析构函数
- 通过operator delete释放内存,其内部使用的是free()函数

Array new
array new内存分配的过程:
- 编译器分配一块内存,内存的首部cookie记录了对象内存分配的信息,首部后面紧跟着3个连续的对象内存
- 为每个对象内存调用构造函数

释放内存时,需要使用delete[]。如果使用delete,则只会调用第一个对象的析构函数,不会调用所有对象的析构函数。上图中的new string[3]是一个例子,只使用delete,将会导致只调用str[0]的析构函数,str[1]、str[2]的析构函数不会被调用,此时就会出现问题。
数组对象的创建与析构过程如下:

构造函数调用顺序是按照构建对象来的,但是析构函数执行是按照相反的顺序。
placement new
placement new的语法为:
1
| new (address) Type(constructor_args...);
|
表示在address这块已有的内存上调用Type的构造函数。示例如下:

没有placement delete,因为placement new没有分配新内存。
重载
C++内存分配的途径
C++内存分配的途径如下图所示,没有重载会走路线二。如果类中重载了operator new(),那么会走路线一。但最终都会调用系统的::operator new()函数。

容器中的内存分配途径如下图所示,容器通过std::allocator实现内存的分配与回收,最终也是调用的::operator new()函数。

重载new和delete
重载::operator new/::operator delete
使用内联函数重载::operator new和::operator delete:

重载operator new/operator delete
如果是在类中重载operator new()方法,该方法可以有多种形式,但是函数参数列表第一个参数必须是size_t类型变量。对于operator delete(),第一个参数必须是void*类型,第二参数size_t是可选项,可以去掉。

重载operator new[]/operator delete[]
![重载operator new[]/operator delete[]](local_operator_new_array_override.png)
测试案例
测试一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include <cstddef> #include <iostream> #include <string> using namespace std;
namespace jj06 {
class Foo { public: int _id; long _data; string _str;
public: static void* operator new(size_t size); static void operator delete(void* deadObject, size_t size); static void* operator new[](size_t size); static void operator delete[](void* deadObject, size_t size);
Foo() : _id(0) { cout << "default ctor. this=" << this << " id=" << _id << endl; } Foo(int i) : _id(i) { cout << "ctor. this=" << this << " id=" << _id << endl; } ~Foo() { cout << "dtor. this=" << this << " id=" << _id << endl; }
};
void* Foo::operator new(size_t size) { Foo* p = (Foo*)malloc(size); cout << "Foo::operator new(), size=" << size << "\t return: " << p << endl;
return p; }
void Foo::operator delete(void* pdead, size_t size) { cout << "Foo::operator delete(), pdead= " << pdead << " size= " << size << endl; free(pdead); }
void* Foo::operator new[](size_t size) { Foo* p = (Foo*)malloc(size); cout << "Foo::operator new[](), size=" << size << "\t return: " << p << endl;
return p; }
void Foo::operator delete[](void* pdead, size_t size) { cout << "Foo::operator delete[](), pdead= " << pdead << " size= " << size << endl;
free(pdead); }
void test_overload_operator_new_and_array_new() { cout << "\ntest_overload_operator_new_and_array_new().......... \n";
cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
{ Foo* p = new Foo(7); delete p;
Foo* pArray = new Foo[5]; delete[] pArray; }
{ cout << "testing global expression ::new and ::new[] \n";
Foo* p = ::new Foo(7); ::delete p;
Foo* pArray = ::new Foo[5]; ::delete[] pArray; } } }
int main(void) { jj06::test_overload_operator_new_and_array_new(); return 0; }
|
测试二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| #include <vector> #include <cstddef> #include <iostream> #include <string> using namespace std;
namespace jj07 {
class Bad { }; class Foo { public: Foo() { cout << "Foo::Foo()" << endl; } Foo(int) { cout << "Foo::Foo(int)" << endl; }
void* operator new(size_t size){ cout << "operator new(size_t size), size= " << size << endl; return malloc(size); }
void* operator new(size_t size, void* start){ cout << "operator new(size_t size, void* start), size= " << size << " start= " << start << endl; return start; }
void* operator new(size_t size, long extra){ cout << "operator new(size_t size, long extra) " << size << ' ' << extra << endl; return malloc(size + extra); }
void* operator new(size_t size, long extra, char init){ cout << "operator new(size_t size, long extra, char init) " << size << ' ' << extra << ' ' << init << endl; return malloc(size + extra); }
void operator delete(void*, size_t) { cout << "operator delete(void*,size_t) " << endl; }
void operator delete(void*, void*) { cout << "operator delete(void*,void*) " << endl; }
void operator delete(void*, long) { cout << "operator delete(void*,long) " << endl; }
void operator delete(void*, long, char) { cout << "operator delete(void*,long,char) " << endl; }
private: int m_i; };
void test_overload_placement_new() { cout << "\n\n\ntest_overload_placement_new().......... \n";
Foo start;
Foo* p1 = new Foo; Foo* p2 = new (&start) Foo; Foo* p3 = new (100) Foo; Foo* p4 = new (100, 'a') Foo;
Foo* p5 = new (100) Foo(1); Foo* p6 = new (100, 'a') Foo(1); Foo* p7 = new (&start) Foo(1); Foo* p8 = new Foo(1); } }
int main(void) { jj07::test_overload_placement_new(); return 0; }
|
pre-class allocator
为每个类设计内存管理工具。
示例1

测试:

代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <cstddef> #include <iostream> using namespace std;
namespace jj04 {
class Screen { public: Screen(int x) : i(x) { }; int get() { return i; }
void* operator new(size_t); void operator delete(void*, size_t);
private: Screen* next; static Screen* freeStore; static const int screenChunk; private: int i; }; Screen* Screen::freeStore = 0; const int Screen::screenChunk = 24;
void* Screen::operator new(size_t size) { Screen *p; if (!freeStore) { size_t chunk = screenChunk * size; freeStore = p = reinterpret_cast<Screen*>(new char[chunk]); for (; p != &freeStore[screenChunk - 1]; ++p) p->next = p + 1; p->next = 0; } p = freeStore; freeStore = freeStore->next; return p; }
void Screen::operator delete(void *p, size_t) { (static_cast<Screen*>(p))->next = freeStore; freeStore = static_cast<Screen*>(p); }
void test_per_class_allocator_1() { cout << "\ntest_per_class_allocator_1().......... \n";
cout << sizeof(Screen) << endl;
size_t const N = 100; Screen* p[N];
for (int i = 0; i< N; ++i) p[i] = new Screen(i);
for (int i = 0; i< 10; ++i) cout << p[i] << endl;
for (int i = 0; i< N; ++i) delete p[i]; } }
int main(void) { jj04::test_per_class_allocator_1(); return 0; }
|
内存池本质上是分配了一大块内存,然后将该内存分割为多个小块通过链表拼接起来,所以物理上不一定连续,但是逻辑上是连续的。
示例2

测试:

代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| #include <cstddef> #include <iostream> using namespace std;
namespace jj05 {
class Airplane { private: struct AirplaneRep { unsigned long miles; char type; }; private: union { AirplaneRep rep; Airplane* next; }; public: unsigned long getMiles() { return rep.miles; } char getType() { return rep.type; } void set(unsigned long m, char t) { rep.miles = m; rep.type = t; } public: static void* operator new(size_t size); static void operator delete(void* deadObject, size_t size); private: static const int BLOCK_SIZE; static Airplane* headOfFreeList; };
Airplane* Airplane::headOfFreeList; const int Airplane::BLOCK_SIZE = 512;
void* Airplane::operator new(size_t size) { if (size != sizeof(Airplane)) return ::operator new(size);
Airplane* p = headOfFreeList;
if (p) headOfFreeList = p->next; else { Airplane* newBlock = static_cast<Airplane*> (::operator new(BLOCK_SIZE * sizeof(Airplane))); for (int i = 1; i < BLOCK_SIZE - 1; ++i) newBlock[i].next = &newBlock[i + 1]; newBlock[BLOCK_SIZE - 1].next = 0;
p = newBlock; headOfFreeList = &newBlock[1]; } return p; }
void Airplane::operator delete(void* deadObject, size_t size) { if (deadObject == 0) return; if (size != sizeof(Airplane)) { ::operator delete(deadObject); return; }
Airplane *carcass = static_cast<Airplane*>(deadObject);
carcass->next = headOfFreeList; headOfFreeList = carcass; }
void test_per_class_allocator_2() { cout << "\ntest_per_class_allocator_2().......... \n";
cout << sizeof(Airplane) << endl;
size_t const N = 100; Airplane* p[N];
for (int i = 0; i< N; ++i) p[i] = new Airplane;
p[1]->set(1000, 'A'); p[5]->set(2000, 'B'); p[9]->set(500000, 'C'); cout << p[1] << ' ' << p[1]->getType() << ' ' << p[1]->getMiles() << endl; cout << p[5] << ' ' << p[5]->getType() << ' ' << p[5]->getMiles() << endl; cout << p[9] << ' ' << p[9]->getType() << ' ' << p[9]->getMiles() << endl;
for (int i = 0; i< 10; ++i) cout << p[i] << endl;
for (int i = 0; i< N; ++i) delete p[i]; } }
int main(void) { jj05::test_per_class_allocator_2(); return 0; }
|
union(联合体):所有成员共享同一块内存,大小=最大成员大小(加上可能的补充)
使用union保存链表元素的next指针,这样可以节省空间。delete时,没有直接删除目标元素,而是将它作为下一个可以分配的内存空间。
static allocator



代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
| #include <cstddef> #include <iostream> #include <complex> using namespace std;
namespace jj09 {
class allocator { private: struct obj { struct obj* next; }; public: void* allocate(size_t); void deallocate(void*, size_t); void check();
private: obj* freeStore = nullptr; const int CHUNK = 5; };
void* allocator::allocate(size_t size) { obj* p;
if (!freeStore) { size_t chunk = CHUNK * size; freeStore = p = (obj*)malloc(chunk);
for (int i = 0; i < (CHUNK - 1); ++i) { p->next = (obj*)((char*)p + size); p = p->next; } p->next = nullptr; } p = freeStore; freeStore = freeStore->next;
return p; } void allocator::deallocate(void* p, size_t) { ((obj*)p)->next = freeStore; freeStore = (obj*)p; } void allocator::check() { obj* p = freeStore; int count = 0;
while (p) { cout << p << endl; p = p->next; count++; } cout << count << endl; }
class Foo { public: long L; string str; static allocator myAlloc; public: Foo(long l) : L(l) { } static void* operator new(size_t size) { return myAlloc.allocate(size); } static void operator delete(void* pdead, size_t size) { return myAlloc.deallocate(pdead, size); } }; allocator Foo::myAlloc;
class Goo { public: complex<double> c; string str; static allocator myAlloc; public: Goo(const complex<double>& x) : c(x) { } static void* operator new(size_t size) { return myAlloc.allocate(size); } static void operator delete(void* pdead, size_t size) { return myAlloc.deallocate(pdead, size); } }; allocator Goo::myAlloc;
void test_static_allocator_3() { cout << "\n\n\ntest_static_allocator().......... \n";
{ Foo* p[100];
cout << "sizeof(Foo)= " << sizeof(Foo) << endl; for (int i = 0; i<23; ++i) { p[i] = new Foo(i); cout << p[i] << ' ' << p[i]->L << endl; }
for (int i = 0; i<23; ++i) { delete p[i]; } }
{ Goo* p[100];
cout << "sizeof(Goo)= " << sizeof(Goo) << endl; for (int i = 0; i<17; ++i) { p[i] = new Goo(complex<double>(i, i)); cout << p[i] << ' ' << p[i]->c << endl; }
for (int i = 0; i<17; ++i) { delete p[i]; } } } }
int main(void) { jj09::test_static_allocator_3(); return 0; }
|


global allocator
上面自定义的分配器使用一条链表来管理内存,而标准库却用了多条链表来进行管理:

new handler
如果用户申请内存时,因为系统原因或申请内存过大导致失败,这是将抛出异常。operator new()函数内部将会调用_calnewh()函数,这个函数通过左边的typedef传入,可以根据需要自己编写handler处理函数来处理该问题。一般有两种方案处理:
- 让更多的Memory可用
- 直接abort()或exit()


=default和=delete
有默认版本的函数有:
- 拷贝构造函数
- 拷贝赋值函数
- 析构函数


参考
CPP-Memory-Management
侯捷-C++内存管理机制