抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

CodingStudio

努力进步

引言

侯捷老师C++内存管理学习笔记


1 基础

C++使用内存的方式

分配 释放 类型 可否重载
malloc() free() C函数 不可
new delete C++表达式 不可
::operator new() ::operator delete() C++函数
allocator<T>::allocate() allocator<T>::deallocate() C++标准库 可自由设计并以之搭配各种容器

1.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
void* p1 = malloc(512);     // 512bytes
free p1;

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

void* p3 = ::operator new(512); // 512 bytes,其中实质为调用 malloc 和 free
::operator delete(p3);

// 以下使用C++标准库提供的allocators
// 器接口具有标准规范,但实现规范并非完全遵守,下面三者形式不同
#ifdef _MSC_VER
// 以下两函数都是 non-static ,定要通过object调用,以下分配3个ints
int* p4 = allocator<int>().allocate(3, (int*)0);
allocator<int>().deallocate(p4,3);
#endif
#ifdef _BORLANDC_
// 以下两函数都是 non-static ,定要通过object调用,以下分配5个ints
int* p4 = allocator<int>().allocate(5);
allocator<int>().deallocate(p4,5);
#endif

// GUNC 2.7
#ifdef _GUNC_
// 以下两函数都是 non-static ,定要通过object调用,以下分配512bytes
int* p4 = allocator<int>().allocate(512);
allocator<int>().deallocate(p4,512);
#endif
// 使用分配器的困扰,在释放时需要记住分配时的分配个数,只有容器可以做到

// GUNC 4.9
// GUNC的分配的函数的用法改变了,且名字也发生了改变
#ifdef _GUNC_
// 以下两函数都是 non-static ,定要通过object调用,以下分配7个ints
void* p4 = allocator<int().allocate(7);
allocator<int().deallocate((int*)p4,7)

// 以下两函数都是 non-static ,定要通过object调用,以下分配9个ints
void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9);
__gnu_cxx::__pool_alloc<int>().deallocate((int*)p5,9)
#endif

1.2 基本构件 new delete expression

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
// --------------
// new expression
// --------------
Complex* pc = new Complex(1,2);
// 编译器转换为
Complex *pc;
try{
void* mem = operator new(sizeof(Complex)); // alloc
pc = static_cast<Complex*>(mem); // cast
pc -> Complex::Complex(1,2); // construct
// 注意只有编译器才可以像上面那样直接呼叫ctor
// 欲直接调用ctor,可以连用placement new:new(p) Complex(1,2);
}catch(std::bad_alloc){
// 若allocation失败就不执行constructor
}

// 其中operator new的源代码..\vc98\crt\src\newop2.cpp
// std::nothrow_t&为保证不抛出异常的操作
void* operator new(size_t size, const std::nothrow_t&)
__THROW0(){
// try to allocate size bytes
void *p;
while((p = malloc(size) == 0)){
// buy morw memory or return null pointer
_TRY_BEGIN
if(_callnewh(size) == 0) break; //new handle 当内存分配完时,会释放掉一些不重要的内存(C++的机制)
_CATCH(std::bad_alloc) return(0);
_CATCH_END
}
return(p);
}

// ------------------
// delete expression
// ------------------
Complex* pc = new Complex(1,2);
...
delete pc;
// 编译器转换为
pc->~Complex() // 先析构
operator delete(pc); // 然后释放内存

// 其中operator delete的源码,..\vc98\crt\src\delop.cpp
void __cdecl operator delete(void *p) _THROW0(){
// free an allocated object
free(p);
}
  • 构造函数和析构函数不能通过指针直接调用(在GNC4.9中)
    • 但在vc中可以正常通过指针调用构造函数和析构函数,但在string中无法使用

1.3 Array new

  • 不对应写new和delete可能会导致内存泄漏
1
2
3
4
5
6
Complex* pca = new Complex[3];
// 唤起三次ctor
// 无法借由参数给予初值
...
delete[] pca; // 唤起3次dtor
// 若使用 delete pca; 则只会调用1次dtor;
  • 没对每个对象调用dtor,有什么影响
    • 对class without ptr member可能没有影响
    • 对class with pointer member通常有影响

dtor

  • 在array new时,系统会分配一个cookie记录着整块的长度等重要的信息(malloc和free设计的)
    • 在左边的例子中,cookie的长度不会发生变化,在delete时不加[]不会产生影响
    • 在右边的例子中,string的设计中带有指针,因此在delete时不加[]时,其绿色的str1,str2和str3会被释放,但其中指针指向的区域不能被正确释放,产生内存泄漏
  • 在构造时,顺序构造,析构时,逆反析构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// array在构造时,没办法赋初值,因此必须有默认构造函数
class A{
public:
int id;
A() :id(0){cout << "default ctor.this=" << this << "id=" << endl;}
A(int i) :id(i){cout << "default ctor.this=" << this << "id=" << endl;}
~A(){cout << "dtor.this=" << this << "id=" << endl;}
}

A* buf = new A[size]; // 调用3次ctor
A* tmp = buf;

cout << "buf=" << buf " tmp=" << tmp << endl;

for(int i = 0; i<sizze;++i){
new (tmp++)A(i); // ctor 3次
}
cout << "buf=" << buf " tmp=" << tmp << endl;

delete[] buf; // 调用3次析构函数,次序逆反

array size

  • 在创建array时会具有上cookie(32bytes)和下cookie(4bytes)记录整块的大小,并且在整块内存上需要调整至16的倍数(本例中添加12bytes)

array size

  • 在创建array时,其中array内容为对象,且对象的析构函数有意义时,内存布局发生变化(会增加一个对象个数的字段),使用delete释放会报错,需要使用delete[]

  • placement new

    • placement new或指new(p),或::operator new(size,void*)
    • placement new允许将object建立在已经分配的内存中,即定点创建,等同于调用构造函数
    • 没有placement delete,因为placement new根本没有分配内存(用placement new对应的operator delete为placement delete)

placement new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <new>
char* buf = new char[sizeof(Complex)*3];
Complex* pc = new(buf)Complex(1,2);
...
delete[] buf;

// 上述第3行编译器转换为
Complex* pc;
try{
void* mem = operator new(sizeof(Complex),buf); // allocate
pc = static_cast<Complex*>(mem); // cast
pc->Complex::Complex(1,2); // construct
}catch(std::bad_alloc){
// 若alloc失败就不执行constructor
}

// operator new调用
void* operator new(size_t, void* loc){return loc;}

1.4 重载

  • C++应用程序分配内存的途径
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
// C++应用程序分配内存的途径
Foo* p = new Foo(x);
...
delete p;

// 表达式不可更改,不可以重载
// 在编译器中转换为
Foo* p = (Foo*)operator new (sizeof(Foo));
new (p)Foo(x);
...
p->~Foo();
operator delete(p);

// 上述代码中 operator new 和 operator delete 其有两条路线可走
// 其中一条为全局的函数,调用
::operator new(size_t);
::operator delete(void*);
// 以上代码可重载,但很少见
// 其调用的为CRT函数
malloc(size_t);
free(void*)

// 另一条路线为调用成员函数(优先权较高)
// 可重载
Foo::operator new(size_t);
Foo::operator delete(void*);
// 内存管理:可自己开辟内存池做小切割,避免cookie浪费资源

// 可以利用以下代码模拟编译器的代码
Foo* p = (Foo*)malloc(sizeof(Foo));
new (p)Foo(x);
...
p->~Foo();
free(p);

容器

  • C++容器分配内存的途径
    • 其中容器的allocate()与deallocate()调用由std::allocator继承而来的new_allocator(后面第二讲着重讲解);其中,还是需要调用::operator new(size_t)与::operator delete(void*)函数调用CRT函数
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
// 重载::operator new / ::operator delete
// 重载全局版本影响无解
void* myAlloc(size_t size){
return malloc(size);
}

void myFree(void* ptr){
return free(ptr);
}

// 它们不可以被声明于一个namespace
inline void* operator new(size_t size){
cout << "jjhou global new() \n";
return myAlloc(size);
}
inline void* operator new[](size_t size){
cout << "jjhou global new[]() \n";
return myAlloc(size);
}
inline void* operator delete(void* ptr){
cout << "jjhou global delete() \n";
return myFree(ptr);
}
inline void* operator delete[](void* ptr){
cout << "jjhou global delete[]() \n";
return myFree(ptr);
}

// ---------------------------------------------------
// 源代码中的版本
// ---------------------------------------------------
void* operator new(size_t size, const std::nothrow_t&)
__THROW0(){
// try to allocate size bytes
void *p;
while((p = malloc(size) == 0)){
// buy morw memory or return null pointer
_TRY_BEGIN
if(_callnewh(size) == 0) break; //new handle 当内存分配完时,会释放掉一些不重要的内存(C++的机制)
_CATCH(std::bad_alloc) return(0);
_CATCH_END
}
return(p);
}

void __cdecl operator delete(void *p) _THROW0(){
// free an allocated object
free(p);
}

// 根据上文对象的 new与delete在编译器中的转换代码
// 更重要的是在对象中进行重载
class Foo{
public:
void* operator new(size_t);
void operator delete(void*,size_t); // size_t为可选参数
// ...
}
// 两个函数为 static 静态函数,调用该函数为正在创建对象的过程中,此时没有对象,故需要为静态函数类型(不通过对象调用)
// 在C++中该两个函数默认为静态函数,故有些情况可不加 static 声明,但实际为静态函数

// 重载array版本
class Foo{
public:
void* operator new[](size_t);
void operator delete[](void*,size_t); // size_t为可选参数
// ...
}

1.4.1 重载示例(上)

  • 在对象创建时会先调用构造函数再调用operator new函数;再析构时,会先调用operator delete函数再调用析构函数
    • 使用::new 或 ::delete 会绕过所有对象重载的版本,调用全局版本
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
class Foo{
public:
int _id;
long _data;
string _str;
public:
Foo() : _id(0) {cout << "deafult ctor.this=" << this << "id=" << _id << endl;}
Foo(int i) : _id(i) {cout << "ctor.this=" << this << "id=" << _id << endl;}

// virtual
~Foo() {cout << "dtor.this=" << this << "id=" << _id << endl;}
// 加上virtual会使对象变大一些

static void* operator new(size_t size);
static void* operator delete(void* pdead, size_t size);
static void* operator new[](size_t size);
static void* operator delete[](void* pdead, size_t size);
};

void* Foo::operator new(size_t size){
Foo* p = (Foo*)malloc(size);
cout << ....
return p;
}
void* Foo::operator delete(void* pdead, size_t size){
cout << ....
free(pdead);
}
void* Foo::operator new[](size_t size){
Foo* p = (Foo*)malloc(size);
cout << ....
return p;
}
void* Foo::operator delete[](void* pdead, size_t size){
cout << ....
free(pdead);
}

Foo* pf = new Foo;
delete pf;

// 下面版本绕过对象的重载版本,强制调用全局版本
Foo* pf = ::new Foo;
::delete pf;

1.4.2 重载实例(下)

  • 重载new()/delete()
  • 可以重载class member operator new()产生多个版本
    • 前提是每一个版本都必须由独特的参数列,且第一个参数必须为size_t,其余参数是以new所指定的placement arguments为初值
    • 出现于new(…)小括号内的便是所谓的placement arguments

Foo* pf = new(300,‘c’)Foo;

  • 可以重载class member operator delete()产生多个版本
    • 绝不会被delete调用,只有当new所调用的ctor抛出异常时才会调用重载版本的operator delete()
    • 只可以被上述方式调用,主要用于处理未能完全创建成功对象的内存
    • operator delete与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
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
class Foo{
public:
Foo() {cout << "Foo::Foo()" << endl;}
Foo(int) {cout << "Foo::Foo(int)" << endl;throw bad;} // 刻意抛出异常,测试placement operator delete

// 版本1:一般的operator new()的重载
void* operator new(size_t size){
return malloc(size);
}
// 版本2:标准库提供的placement new()的重载
void* operator new(size_t size, void* start){
return start;
}
// 版本3:崭新的placement new()
void* operator new(size_t size, long extra){
return malloc(size+extra);
}
// 版本4:一个placement new
void* operator new(size_t size, long extra, char init){
return malloc(size+extra);
}
// 版本5:一个placement new;故意写错第一个参数
// ! void* operator new(long extra, char init){
// return malloc(extra);
// }

private:
int m_i;
};

// 在G4.9中没有调用operator delete(void* long),但G2.9中确实调用

// 标准库中string实质为basic_string
// 在其中重载了operator new与operator delete
// string在创建时会增加一段extra内存,其多带了一段内容reference
template<...>
class basic_string{
private:
struct Rep{
...
void release() {if(--ref==0) delete this;}
inline static void* operator new(size_t, size_t);
inline static void operator delete(void*);
inline static Rep* create(size_t);
...
}
};

template<class charT, class traits, class Allocator>
inline void* basic_string<charT,traits,Allocator>::Rep::operator new(size_t s, size_t extra){
return Allocator::allocate(s+extra * sizeof(charT));
}
template<class charT, class traits, class Allocator>
inline void* basic_string<charT,traits,Allocator>::Rep::operator delete(void* ptr){
// ...
}
template<class charT, class traits, class Allocator>
inline basic_string<charT,traits,Allocator>::Rep* basic_string<charT,traits,Allocator>::Rep::create(size_t extra){
extra = frob_size(extra+1);
Rep *p = new(extra)Rep;
...
return p;
}

string结构

1.5 Per-class 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <cstddef>
#include <iostream>
using namespace std;

class Screen {
public:
Screen(int x) : i(x) { };
int get() { return i; }

void* operator new(size_t);
void operator delete(void*, size_t);
private:
// 这种设计会引起多耗用一个next
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) {
//linked list 是空的,所以攫取一大塊 memory
//以下呼叫的是 global operator new
size_t chunk = screenChunk * size;
freeStore = p =
reinterpret_cast<Screen*>(new char[chunk]);
//將分配得來的一大塊 memory 當做 linked list 般小塊小塊串接起來
for (; p != &freeStore[screenChunk-1]; ++p)
p->next = p+1;
p->next = 0;
}
p = freeStore;
freeStore = freeStore->next;
return p;
}


// 没有对应的delete,但这不算内存泄漏
void Screen::operator delete(void *p, size_t)
{
//將 deleted object 收回插入 free list 前端
(static_cast<Screen*>(p))->next = freeStore;
freeStore = static_cast<Screen*>(p);
}

//-------------
// 测试程序
// ------------
cout << sizeof(Screen) << endl; //8

size_t const N = 100;
Screen* p[N];

for (int i=0; i< N; ++i)
p[i] = new Screen(i);

//輸出前 10 個 pointers, 用以比較其間隔
for (int i=0; i< 10; ++i)
cout << p[i] << endl;

for (int i=0; i< N; ++i)
delete p[i];

//-------
// 版本2
//-------
class Airplane { //支援 customized memory management
private:
struct AirplaneRep {
unsigned long miles;
char type;
};
private:
union {
AirplaneRep rep; //此針對 used object
Airplane* next; //此針對 free list
};
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)
{
// 如果大小錯誤,轉交給 ::operator new()
// 如果由继承发生,则有可能产生错误
if (size != sizeof(Airplane))
return ::operator new(size);

Airplane* p = headOfFreeList;

//如果 p 有效,就把list頭部移往下一個元素
if (p)
headOfFreeList = p->next;
else {
//free list 已空。配置一塊夠大記憶體,
//令足夠容納 BLOCK_SIZE 個 Airplanes
Airplane* newBlock = static_cast<Airplane*>
(::operator new(BLOCK_SIZE * sizeof(Airplane)));
//組成一個新的 free list:將小區塊串在一起,但跳過
//#0 元素,因為要將它傳回給呼叫者。
for (int i = 1; i < BLOCK_SIZE-1; ++i)
newBlock[i].next = &newBlock[i+1];
newBlock[BLOCK_SIZE-1].next = 0; //以null結束

// 將 p 設至頭部,將 headOfFreeList 設至
// 下一個可被運用的小區塊。
p = newBlock;
headOfFreeList = &newBlock[1];
}
return p;
}

// operator delete 接獲一塊記憶體。
// 如果它的大小正確,就把它加到 free list 的前端
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;
}

//-------------
// 测试代码
//-------------
cout << sizeof(Airplane) << endl; //8

size_t const N = 100;
Airplane* p[N];

for (int i=0; i< N; ++i)
p[i] = new Airplane;


//隨機測試 object 正常否
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;

//輸出前 10 個 pointers, 用以比較其間隔
for (int i=0; i< 10; ++i)
cout << p[i] << endl;

for (int i=0; i< N; ++i)
delete p[i];

1.6 static allocator

  • 当需要为不同的classes重写一遍几乎相同的member operator new和member operator delete时,应该有方法将一个总是分配特定尺寸区域的memory allocator概念包装起来,使之容易被重复使用
    • 每个allocator object都是个分配器,内部有一个free-lists;不同的allocator objects维持不同的free-lists
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
class allocator{
public:
struct obj{
struct obj* next; // 嵌入式指针
}
public:
void* allocate(size_t);
void deallocate(void*, size_t);
private:
obj* freeStore = nullptr;
const int CHUNK = 5; // 小一些以便观察;标准库为20
}

// 使每CHUNK个元素的空间是相邻的
void* allocator::allocate(size_t size){
obj* p;
if(!freeStore){
// linked list 为空,于是申请一大块
size_t chunk = CHUNK * size;
freeStore = p = (obj*)malloc(chunk);

// 将分配得来的一大块当作linked list般小块小块串接起来
for(int i = 0; i<(CHUNK-1);++i){
p->next = (obj*)((char*)p+size);
p = p->next;
}
p->next = nullptr; // last
}
p = freeStore;
freeStore = freeStore->next;
return p;
}

void* allocator::deallocate(void* p, size_t){
// 将*p回收插入free list前端
((obj*)p)->next = freeStore;
freeStore = (obj*)p;
}

// 使用上述allocator
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; // 由于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; // 由于myAlloc为静态类型,故需要在类外定义

// 测试程序
Foo* p[100];
cout << "sizeof(Foo)=" << sizeof(Foo) << endl;
for (int i=O; i<23; ++i) { //随意看看秸果
p[i] =new Foo(i);
cout << p[i] << '' << p[i]->L << endl;
}
for (int i=O; i<23; ++i) {
delete p[i];
}

1.7 macro for 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
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; // 由于myAlloc为静态类型,故需要在类外定义

// 以上写法非常固定,可以将其转换为macro写法
// DECLARE_POOL_ALLOC
#define DECLARE_POOL_ALLOC() \
public: \
void* operator new(size_t size){return myAlloc.allocate(size);}
void* operator delete(void* pdead, size_t size){return myAlloc.deallocate(pdead, size);}
protected:
static allocator myAlloc;

// IMPLEMENT_POOL_ALLOC
#define IMPLEMENT_POOL_ALLOC(class_name) \
allocator class_name::myAlloc;

// 可将上述类的写法转换为
class Foo{
DECLARE_POOL_ALLOC()s
public:
long L;
string str;
public:
Foo(long l) :L(l){}
};
IMPLEMENT_POOL_ALLOC(Foo)

// 使用上述程序进行测试,与上述版本的结果一致

1.8 global allocator

  • 将版本3的allocator进一步发展为16条free-lists,并且不再以application classes内的static呈现,而是标准库的global allocator

global allocator

1.9 new handler

  • 在内存分配结束之后,需要检查内存是否分配成功
    • 当operator new没能力分配申请的内存,会抛出std::bad_alloc 异常。某些老编译器返回0

new (nothrow) Foo;
可以指定程序返回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
// 抛出异常之前,会先调用一个可由client指定的handler
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
// 设计良好的new handler只有两个选择
// 让更多内存可用
// 调用abort或exit()

// ---------------------------------------------------
// 源代码中的版本
// ---------------------------------------------------
void* operator new(size_t size, const std::nothrow_t&)
__THROW0(){
// try to allocate size bytes
void *p;
while((p = malloc(size) == 0)){
// buy morw memory or return null pointer
_TRY_BEGIN
if(_callnewh(size) == 0) break; //new handle 当内存分配完时,会释放掉一些不重要的内存(C++的机制)
_CATCH(std::bad_alloc) return(0);
_CATCH_END
}
return(p);
}
// 其中_callnewh为调用new handler函数

// -------
// 使用实例
// -------
#include <new>
#include <iostream>
#include <cassert>
using namespace std;

void noMoreMemory(){
cerr << "out of memory";
abort();
}

void main(){
set_new_handler(noMoreMemory);
int* p = new int[10000000000000]; // well, so big
assert(p);
}
// 本例中 new handler 若无调用abort(),执行后cerr会不断出现"out of memory",需要强制中断(原因在于会不断调用new handler直至获得足够的内存)

1.10 =default,=delete

  • 在C++中只有拷贝构造函数,拷贝赋值函数,析构函数具有编译器合成版本(默认版本)
1
2
3
4
5
6
7
8
class Foo{
public:
Foo() = default;
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
~Foo() = delete;
...
};

2 std::allocator

2.1 VC6 malloc()

vc6 malloc

  • 内存管理的目的:管理效率提高,内存利用率提高(去除cookie)

2.2 VC6标准分配器的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef _FARQ
#define _FARQ
#define _PDFT ptrdiff_t
#define SIZT size t
#endif

template<class _Ty>
class allocator{
public:
typedef _SIZT size_type;
typedef _PDFT difference_type;
typedef _Ty _FARQ *pointer;
typedef _Ty value_type;
pointer allocate(size_type _N, const void *){return (_Allocate((difference_type) _N, (pointer)0));}
void deallocate(void _FARQ * _P, size_type){operator delete(_P);}
}

template<class _Ty>inline _Ty _FARQ * _Allocate(_PDFT _N, _Ty _FARQ *){
if (_N < 0) _N = 0;
return ((_Ty _FARQ*)operator new((_SIZT)_N * sizeof(_Ty)));
}
// VC6的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计

5 the others

5.1 const

  • 当成员函数的const和non-const版本同时存在
    • const对象只能调用const版本
    • non-const对象只能调用non-const版本
  • const member functions保证不更改data members
    • non-const member functions不保证data members不变
  • non-const object可调用const members functions,反之常量对象调用非常量函数不行

评论