文章目录
在开始内存管理学习之前,可以先看一下C++的强制转型工具:C++强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast。
1. C++使用内存可用的原始工具
1.1 C++应用(application)使用内存(memory)的途径
1.2 C++ memory primitives 类别及是否可以重载
分配 | 释放 | 类属 | 可否重载 |
---|---|---|---|
malloc() | free() | C函数 | 不可 |
new | delete | C++表达式(expression) | 不可 |
::operator new() | ::operator delete() | C++函数 | 可 |
allocator::allocate() | allocator::deallocate() | C++标准库(std) | 可自由设计使其搭配任何容器 |
1.3 primitives 的使用
#include <iostream>
#include <complex>
#include <memory> //std::allocator
#include <ext\pool_allocator.h> //欲使用 std::allocator 以外的 allocator, 就得自行 #include <ext/...>
using namespace std;
namespace jj01
{
void test_primitives()
{
cout << "\ntest_primitives().......... \n";
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。
//其接口雖有標準規格,但實現廠商並未完全遵守;下面三者形式略異。
//以下分别是MicroSoft C,Borland C和GNU C的调用实现
#ifdef _MSC_VER
//以下兩函數都是 non-static,定要通過 object 調用。以下分配 3 個 ints.
//注意由于使用的是object调用,且object是栈中变量,所以语句结束即调用~object()函数,即自动调用dtor
//下边使用object调用的同理
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
#ifdef __GNUC__
//以下兩函數都是 static,可通過全名調用之。以下分配 512 bytes.
//GNU 2.9 version下的分配器
//void* p4 = alloc::allocate(512);
//alloc::deallocate(p4,512);
//GNU 4.9 version下的分配器
//以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints.
void* p4 = allocator<int>().allocate(7);
allocator<int>().deallocate((int*)p4,7);
//以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints.
//对应GNU2.9分配器的内容
void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9);
__gnu_cxx::__pool_alloc<int>().deallocate((int*)p5,9);
#endif
}
} //namespace
2. 原始工具详解
2.1 new / delete expression
只有在编译器中才能够使用类作用域符号对类型的构造函数/ctor进行调用。若非如下图所示,而是使用Complex* pc = new Complex::Complex(1,2)
则会报错(在VC6的内置编译器中不会)。
|
|
new 和 delete 表达式即是C++语言中所提供的 new 和 delete 关键字,在编译过程中,编译器将它们分别转化为调用 ::operator new() (即global new)和 ::operator delete()来完成对内存空间的 分配 和 回收 目的。
对于 std::string (类型可以理解为 basic_string 的适配器?待商榷)其成员函数中的构造函数如下。即是 string::basic_string() 而不是 string::string(),这点需要注意。
(constructor) Construct basic_string object (public member function )
此外,除了针对单一对象的 new 和 delete,还有针对对象数组的 array new(new[])和 array delete(delete[])。
对于 array new 和 array delete 它们需要成对使用,详见《Effective C++》条款:16. new 和 delete 要成对使用且形式相同,否则对于对象数组的情况,回收内存时没有使用 delete[] ,则对于没有指针成员的类型不会有问题,但对于有指针成员的类型,则实际上只调用一次 dtor,会导致内存泄漏。
在使用 new[] 时,内存中会含有 cookie 块,记录数组中对象的数量等信息,有时还会有两端都有cookie的情况。同时对于有指针变量的类型的对象数组,new[]得到的指针指向第一个对象的内存地址,使用对应的delete[]时,指针所指位置则直到记录数组内对象数目位置的内存地址。
|
|
delete[]
对对象调用dtor
的顺序和new[]
创建对象调用ctor
的顺序是相反的。
在内存分配中,每次分配内存bytes数若是4的倍数则直接划分,若不是要进行padding补齐为4的倍数?,此外类的大小其中也涉及字节对齐(padding),见:C++类空间大小。
2.2 placement new() & operator delete()
placement new 允许我们将 object 构建于 allocated memory 当中去;(可以理解为定点 new,在指定内存位置进行操作,这也是在标准库所提供的 placement new() 的重载形式)
没有所谓 placement delete,因为 placement new 根本没有分配/获取新的内存,亦或称呼与 placement new 对应的 operator delete 为 placement delete;
2.3 operator new() & operator delete()
对原始工具再进行细分,使用new expression得到指向新的构建对象的内存地址指针的整个过程可分为三步:
- 调用operator new函数;
- 运行对应的构造函数并传初值;
- 分配空间并构造完成后,返回一个指向该对象的指针;
而对于new/delete expression是不可重载的,则意味着我们无法对其进行定制化。而对于operator new()函数是可以重载的,那么可以在其上做文章,同时要注意operator new()又可分为 type::operator new()(member functions)和 global ::operator new()(global functions),后者虽同样可重载但是并不建议,通常作为 type::operator new() 尝试失败后的备选方案,而且其为全局资源,设置重载后可能导致其它类型内存分配的问题。此外,若是更改了::operator new(),则须使用new_handler set_new_handler (new_handler new_p) noexcept;
进行复原。见《Effective C++》条款49. 了解 new-handler 的行为。
expression new的过程,operator new() 的作用时机和分类见下图。
对于全局 ::operator new / ::operator delete() 的重载
对于成员 operator new() / operator delete() 的重载
对于 class member operator new() 的重载,可以写出多个版本,前提是每个版本的声明都有独特的参数列表,参数列表中的第一个参数必须是 size_t,其余参数则以 new 所指定的 placement argument 为初值(即出现在 new(size_t, …)中的…即是所谓的 placement argument)。
对于 class member operator delete() (亦即是placement operator delete)的重载,也可写出多个版本。但是它们绝不会被 delete 调用。只有当 new 所调用的 ctor 抛出 exception 时,才会调用这些重载版的 operator delete(),其只会这样被调用。其用处是用来归还未能完全创建成功的 object 所占用的 memory,可以理解为保持原子性。
若是没有对 operator new(size_t size, …) 对应写出相同 placement argument 的 operator delete(void*, …),也不会有报错,只会有编译警告,同时编译器会默认放弃处理 ctor 所发出的异常。
3. new handler & set_new_handler函数
当 operator new 无法满足分配出所需 memory 大小的需求,会抛出一个 std::bad_alloc exception,而某些老旧编译器则是返回0。同时也可以使用 new(nothrow) 形式的 new operator,即不抛出异常。相关内容可见《Effective C++》条款49. 了解 new-handler 的行为。
设计良好的 new handler 主要有两个目的:
- 让更多的memory可用,也即是内存空间利用率更高;
- 调用 abort() 或 exit();
new handler 中若无调用 abort() 或 exit(),则若是malloc无法分配出所需内存空间,则会重复调用 new handler 函数,若是new handler 也不能跳脱出来,则不断进行 new handler 尝试,造成死循环。使用全局 set_new_handler(void* (*func)())
函数对 new handler 进行设置。
同时C++ 11中引入了 =default 和 =delete ,分别指的是 使用默认功能 和 禁用成员函数,具体内容可见:C++中的 =default和=delete;对其用处的描述为:it is not only for constructors and assignments, but also applies to operator new/new[],operator delete/delete[] and their overloads.