C++内存管理机制-从平地到万丈高楼

tiny_star Lv3

本文包含 primitives(基础工具),malloc / free,std::allocator,other allocators,loki::allocator的底部动作介绍,从最基础的C++语言构件到高知名度的内存管理器

视频学习:侯捷老师《内存管理——从平地到万丈高楼》

资源网站

道格·利亚的工作站

A Memory Allocator

Bibliography(书目志)

《STL源码剖析》by 侯捷 Chap2:allocator

《Small Memory Software》by James Noble & Charles Weir,AW 2001

《Modern C++ Design》by Andrei Alexandrescu Chap4:Small-Object Allocation

Libraries(库)

  • STL Allocators
  • MFC CPlex + CFixedAlloc
  • Boost.Pool
  • Loki SmallObjAllocator
  • VC malloc / free
  • jemalloc
  • tcmalloc

C++应用程序

image-20251202153533288

C++ memory primitives(基础工具)

分配 释放 类属 可否重载
malloc() free() C函数 不可
new delete C++表达式(expressions) 不可
::operator new() ::operator delete() C++函数
allocator<T>::allocate() allocator<T>::deallocate() 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
35
36
37
void* p1 = malloc(512);		// 512 bytes
free(p1);

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

void* p3 = ::operator new(512); // 512 bytes
::operator delete(p3);

// 以下使用 C++ 标准库提供的 allocators
// 其接口虽有标准规格,但实现厂商并未完全遵守;下面三者形式略异
#ifdef _MSC_VER
// 以下两函数都是 non-static,定要通过 object 调用。以下分配3个 ints
int* p4 = allocator<int>().allocate(3, (int*)0); // (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
// alloc 是旧版本,在新版本是 __gnu_cxx::__pool_alloc<int>()
#ifdef __GNUC__
// 以下两函数都是 static,可通过全名调用之。以下分配512 bytes
void* p4 = alloc::allocate(512);
alloc::deallocate(p4, 512);
#endif
// 新版本
#ifdef __GNUC__
// 以下两函数都是 non-static,定要通过 object 调用。以下分配7个 ints
void* p4 = allocator<int>().allocate(7);
allocate<int>().deallocate((int*)p4, 7);

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

new 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
Complex* pc = new Complex(1, 2);

// 编译器转为...
Complex* pc;
try{
void* mem = operaotr new(sizeof(Complex)); // allocate
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
}


// ...\vc98\crt\src\newop2.cpp
// struct std::nothrow_t{}:The struct is used as a function parameter to operator new to indicate that the function should return a null pointer to report an allocation failure, rather than throw an exception
void* operator new(size_t size, const std::nothrow_t&) _THROW0()
{
// try to allocate size bytes
void* p;
while((p = malloc(size)) == 0)
{
// buy more memory or return null pointer
_TRY_BEGIN
if(_callnewh(size) == 0) break;
_CATCH(std::bad_alloc) return (0);
_CATCH_END
}
return (p);
}

delete expression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Complex* pc = new Complex(1, 2);
...
delete pc;

// 编译器转为...
pc->~Complex(); // 先析构,可以直接调用
operator delete(pc); // 然后释放内存


// ...\vc98\crt\src\delop.cpp
void __cdecl operator delete(void* p) _THROW0()
{
// free an allocated object
free(p);
}

Ctor & Dtor 直接调用

1
2
3
4
5
6
string* pstr = new string;
cout << "str= " << *pstr << endl;

//! pstr->string::string("star"); // [Error] 'class std::basic_string<char>' has no member named 'string',string 在标准库中是一个 typedef,真实名字是 basic_string
//! pstr->~string(); // crash,能直接调用 Dtor,但是不能直接调用 Ctor,但是这里没有调用 Ctor,而调用 Dtor 会失败,崩溃掉
cout << "str= " << *pstr << endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
public:
int id;
A(int i) : id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
~A() { cout << "dtor. this=" << this << endl; }
};

A* pA = new A(1); // ctor. this=000307A8 id=1
cout << pA->id << endl; // 1
//! pA->A::A(3); // in VC6 : ctor. this=000307A8 id=3,成功
// in GCC : [Error] cannot call constructor 'star02::A::A' directly,失败,star02是命名空间

//! A::A(5); // in VC6 : ctor. this=0013FF60 id=5 dtor. this=0013FF60
// in GCC : [Error] cannot call constructor 'star02::A::A' directly
// [Note] for a function-style cast, remove the redundant '::A'

cout << pA->id << endl; // in VC6 : 3
// in GCC : 1

delete pA; // dtor. this=000307A8

array new, array delete

1
2
3
4
5
Complex* pca = new Complex[3];
// 唤起三次 ctor
// 无法藉由参数给予初值
...
delete[] pca; // 唤起3次 dtor,没有加 [] 只调用一次 dtor

cookie 记录整块长度,内存泄漏不发生在内存块回收,发生在 dtor(调用次数),因此对默认析构函数的类使用 delete pca,数组本身不会泄漏,泄漏的是指针指向的空间

image-20251202153416487

没对每个 object 调用 dtor,有什么影响
对 class without ptr member 可能没影响
对 class with pointer member 通常有影响(内存泄漏)

1
2
3
string* psa = new string[3];
...
delete psa; // 唤起1次 dtor,内存泄漏
image-20251202153306859
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
class A
{
public:
int id;

A() : id(0) { cout << "default ctor. this=" << this << "id=" << id << endl; }
A(int i) : id(i) { cout << "ctor. this=" << this << "id=" << id << endl; }
~A() { cout << "dtor. this=" << this << "id=" << id << endl; }
};


A* buf = new A[size]; // new 数组无法一一给定初值,调用 default ctor 3 次 [0]先于[1]先于[2])
// A 必须有 default ctor,否则 [Error] no matching function for call to 'A::A()'
A* tmp = buf;

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

for(int i = 0; i < size; ++i)
new(tmp++)A(i); // ctor 3次,placement new,在 tmp 位置调用 ctor 创建对象

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

delete [] buff; // dtor 3次(次序逆反,[2]先于[1]先于[0]


// print
// default ctor. this=0x3e398c id=0
// default ctor. this=0x3e3990 id=0
// default ctor. this=0x3e3994 id=0
// buf=0x3e398c tmp=0x3e3998
// dtor. this=0x3e3994 id=2
// dtor. this=0x3e3990 id=1
// dtor. this=0x3e398c id=0
// gnuc 编译器构造的时候从上往下,析构的时候从下往上
image-20251201235134489

array size, in memory block

1
2
3
4
5
6
int* pi = new int[10];	// sizeof(pi)=4
delete pi; // 因为没有析构函数,对于 int 类型加不加 [] 是无所谓的


int ia[10]; // from stack but not heap
cout<<sizeof(ia); // 40

橙色部分只有在 Debugger mode 下出现,大小确定(32个字节和4个字节)
pi 指向第一个 int 数据即 00441c30 位置
两个 cookie,上下各一个,记录整块大小,cookie 最后一个比特被借用来当一个 on/off 的状态(是否给出去)
pad 用于边界对齐,VC6 要调整到16的边界

image-20251202153123828
1
2
3
4
5
6
7
Demo* p = new Demo[3];	// p 指向 00481c34
delete [] p; // 若未写 [],debug mode 下出现 Assertion Failed
// 注意,重载 operator delete[] 后观察到传入的 p 竟指向 004811c30


Demo d[3]; // from stack but not heap
cout << sizeof(d); // 36

如果放的是 object,其析构函数是重要的(即非默认析构函数),那么编译器在创建一个 array 时就不一样,如上面的 Demo,会把3(数组大小)写入内存块

image-20251201235559263

placement new

  • placement new 允许我们将 object 建构于 allocated memory 中
  • 没有所谓 placement delete,因为 placement new 根本没分配 memory
    亦或称呼于 placement new 对应的 operator delete 为 placement delete
  • 注意,关于“placement new”,或指 new(p),或指 ::operator new(size, void*)(两个参数版本,不做事)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <new>
char* buf = new char[sizeof(Complex)*3]; // new 分配内存
Complex* pc = new(buf)Complex(1, 2); // 这个 new 不会再分配内存
...
delete [] buf;

// 编译器转为(只有第3行 Complex* pc = new(buf)Complex(1, 2); 转换)
Complex* pc;
try {
void* mem = operator new(sizeof(Complex), buf); // allocate,不做事,返回 buf
pc = static_cast<Complex*>(mem); // cast,转型
pc->Complex::Complex(1, 2); // construct,调用构造函数
}catch(std::bad_alloc)
{
// 若 allocation 失败就不执行 constructor
}


void* operator new(size_t, void* loc) // 不做事,返回 loc
{ return loc; }
image-20251202153707384

C++应用程序,分配内存的途径

image-20251202172024062

C++容器,分配内存的途径

image-20251202172109471

重载 ::operator new / ::operator delete

重载全局 operator new 和 operator delete,小心,这影响无远弗届(没有不能到达的地方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void* myAlloc(size_t size)
{ return malloc(size); }

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

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

inline void* operator new[](size_t size)
{ cout << "star global new[]() \n"; return myAlloc(size); }

inline void operator delete(void* ptr)
{ cout << "star global delete() \n"; myFree(ptr); }

inline void operator delete[](void* ptr)
{ cout << "star global delete[] \n"; myFree(ptr); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...\vc98\crt\src\newop2.cpp
void* operator new(size_t size, const std::nothrow_t&) _THROW0()
{
// try to allocate size bytes
void* p;
while((p = malloc(size)) == 0)
{
// buy more memory or return null pointer
_TRY_BEGIN
if(_callnewh(size) == 0) break;
_CATCH(std::bad_alloc) return (0);
_CATCH_END
}
return (p);
}

// ...\vc98\crt\src\delop.cpp
void __cdecl operator delete(void* p) _THROW0()
{
// free an allocated object
free(p);
}

重载 operator new / operator delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Foo* p = new Foo;
// 转换为
try{
void* mem = operator new(sizeof(Foo));
p = static_cast<Foo*>(mem);
p->Foo::Foo();
}

delete p;
// 转换为
p->~Foo();
operator delete(p);


class Foo {
public:
// per-class allocator
void* operator new(size_t);
void operator delete(void*, size_t); // size_t optional
// ...
};

重载 operator new[] / operator delete[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Foo* p = new Foo[N];
// 转换为
try {
void* mem = operator new(sizeof(Foo)*N + 4);
p = static_cast<Foo*>(mem);
p->Foo::Foo(); // N次
}

delete [ ] p;
// 转换为
p->~Foo(); // N次
operator delete(p);


class Foo {
public:
// per-class allocator
void* operator new[](size_t);
void operator delete[](void*, size_t); // size_t optional
};

示例,接口

  • Titre: C++内存管理机制-从平地到万丈高楼
  • Auteur: tiny_star
  • Créé à : 2025-11-13 22:44:54
  • Mis à jour à : 2025-12-02 23:56:15
  • Lien: https://tiny-star3.github.io/2025/11/13/Cpp/C++MemoryManagement/
  • Licence: Cette œuvre est sous licence CC BY-NC-SA 4.0.
Commentaires