学习笔记源自博览网侯捷大师的C++课程,在原视频及讲义的基础上填充注释。
如有侵权,请联系删除,抱歉。
C++内存管理:从平地到万丈高楼
目标:深入源码,学习C++分配内存的基本工具及进阶工具管理内存的方式、STL标准库中分配器管理内存的方式、底层malloc函数管理内存的方式。
从平地到万丈高楼:从最基础的C++语言构件,到高知名度的内存管理器。
基础条件:
- 曾动态分配及使用内存
- 曾使用C++ STL容器
工具:
- Dev-C++
- Visual C++ 6.0(Microsoft Visual Studio)
链接:
- Doug Lea’s Home Page-DL Malloc
书籍:
- STL源码剖析:chapter2-allocator
- Modern C++ Design:chapter4-Small Object Allocation
- Small Memory Software
库:
- STL Allocators
- MFC CPlex + CFixedAlloc
- Boost.Pool
- Loki SmallObjAllocator
- VC malloc/free
- jemalloc
- tcmalloc
- …
一、primitives-基础工具
1.内存分配的四个层面
类别 | 内存分配 | 内存释放 | 可否重载 |
---|---|---|---|
C函数 | malloc() | free() | 不可重载 |
C++表达式(expression) | new | delete | 不可重载 |
C++函数 | ::operator new() | ::operator delete() | 可重载 |
C++标准库 | allocator<T>::allocate() | allocator<T>::deallocate() | 可自由设计 并搭配任何容器 |
new
表达式底层调用operator new()
函数,进而调用malloc()
函数申请内存;
delete
表达式底层调用operator delete()
函数,进而调用free()
函数释放内存。
示例:C++分配内存的基本工具(primitives)
/* 1.使用malloc分配内存 */
void* p1 = malloc(512); // 512字节
free(p1);
/* 2.使用new分配内存 */
complex<int>* p2 = new complex<int>; // 1个对象
delete(p2);
/* 3.使用::operator new()分配内存 */
void* p3 = ::operator new(512); // 512字节
::operator delete(p3);
/* 4.使用C++标准库提供的allocators */
/* 接口存在标准规格,但编译器的实现未完全遵循 */
#ifdef _MSC_VER
// 非静态函数,需通过对象调用,分配3个int型内存
int* p4 = allocator<int>().allocate(3, (int*)0); // 匿名对象调用
allocator<int>().deallocate(p4, 3);
#endif
#ifdef __BORLANDC__
// 非静态函数,需通过对象调用,分配5个int型内存
int* p4 = allocator<int>().allocate(5); // 匿名对象调用
allocator<int>().deallocate(p4, 5);
#endif
#ifdef __GNUC__ // GCC 2.9
// 静态函数,可通过类名调用,分配512个字节内存
void* p4 = alloc::allocate(512); // 类名调用
alloc::deallocate(p4, 512);
#endif
#ifdef __GNUC__ // GCC 4.9
// 非静态函数,需通过对象调用,分配7个int型内存
void* p4 = allocator<int>().allocate(7); // 匿名对象调用
allocator<int>().deallocate((int*)p4, 7);
// 非静态函数,需通过对象调用,分配9个int型内存
void* p5 = __gnu_cxx::_pool_alloc<int>().allocate(9); // 匿名对象调用
__gnu_cxx::_pool_alloc<int>()deallocate((int*)p5, 9);
#endif
注:使用分配器管理内存时,申请和释放的内存大小必须对应。
2.new/delete表达式
2.1 new表达式
new表达式:先调用malloc()
函数申请内存;再调用构造函数。
new
表达式底层调用operator new()
函数,进而调用malloc()
函数申请内存。
示例:编译器调用new表达式的底层操作
Complex* pc = new Complex(1, 2);
Complex *pc;
try {
/* 1.allocate:调用operator new()申请内存 */
// 若没有自定义的operator new()函数,则调用全局::operator new()
void* mem = operator new(sizeof(Complex));
/* 2.cast:静态类型转换 */
pc = static_cast<Complex*>(mem);
/* 3.construct:调用构造函数 */
pc->Complex::Complex(1, 2);
} catch (std::bad_alloc) {
// 若内存分配失败,则不执行构造函数
}
注:通常只有编译器可以直接调用构造函数
pc->Complex::Complex(1, 2);
。若需要直接调用构造函数,可使用
placement new
:new(p) Complex(1, 2);
。
new表达式的原理
malloc()
函数申请内存失败时,会调用_callnewh()
函数,进而调用自定义的new handler。
new handler是可自行设定的回调函数,应在自定义的new handler中释放不必要的内存,以便继续调用malloc()
函数成功申请内存。
参考链接:C++new和delete实现原理
2.2 delete表达式
delete表达式:先调用析构函数;再调用free()
函数释放内存。
delete
表达式底层调用operator delete()
函数,进而调用free()
函数释放内存。
示例:编译器调用delete表达式的底层操作
delete pc;
/* 1.调用析构函数 */
pc->~Complex();
/* 2.调用operator delete()释放内存 */
operator delete(pc);
全局函数::operator delete()的实现
// 路径:..\vc98\crt\src\delop.cpp
void __cdecl operator delete(void *p) _THROW()
{
free(p);
}