|
@@ -0,0 +1,327 @@
|
|
|
|
|
+# 内存管理
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+## Primitives 基础写法
|
|
|
|
|
+
|
|
|
|
|
+### new / delete
|
|
|
|
|
+
|
|
|
|
|
+| 分配 | 释放 | 类型 | 可否重载 |
|
|
|
|
|
+| --- | --- | --- | --- |
|
|
|
|
|
+| malloc() | free() | C函数 | 不可 |
|
|
|
|
|
+| new | delete | C++表达式 | 不可 |
|
|
|
|
|
+| ::operator new() | ::operator delete() | C++函数 | 可 |
|
|
|
|
|
+| allocator<T>::allocate() | allocator<T>::deallocate() | C++标准库 | 可自由设计并与之搭配任何容器 |
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+#include <iostream>
|
|
|
|
|
+#include <complex>
|
|
|
|
|
+
|
|
|
|
|
+using namespace std;
|
|
|
|
|
+
|
|
|
|
|
+int main() {
|
|
|
|
|
+
|
|
|
|
|
+ void* p1 = malloc(521);
|
|
|
|
|
+ free(p1);
|
|
|
|
|
+
|
|
|
|
|
+ complex<int>* p2 = new complex<int>;
|
|
|
|
|
+ delete p2;
|
|
|
|
|
+
|
|
|
|
|
+ void* p3 = ::operator new(512);
|
|
|
|
|
+ ::operator delete(p3);
|
|
|
|
|
+
|
|
|
|
|
+#ifdef _MSC_VER
|
|
|
|
|
+ // 以下两个函数都是non-static,一定要通过object调用,以下分配3个int空间
|
|
|
|
|
+ int* p4 = allocator<int>().allocate(3, (int*)0);
|
|
|
|
|
+ // 上面的 (int*)0 暂时没有用
|
|
|
|
|
+ // 通过 allocator<int>() 创建要给临时对象,执行临死对象的allocate方法
|
|
|
|
|
+ allocator<int>().deallocate(p4, 3);
|
|
|
|
|
+ // 通过 allocator<int>() 创建要给临时对象,执行临死对象的deallocate方法
|
|
|
|
|
+#endif // _MSC_VER
|
|
|
|
|
+#ifdef __BORLANDC__
|
|
|
|
|
+ // 以下两个函数都是non-static,一定要通过object调用,以下分配5个int空间
|
|
|
|
|
+ int* p4 = allocator<int>().allocator(5);
|
|
|
|
|
+ allocator<int>().deallocate(p4, 5);
|
|
|
|
|
+#endif // __BORLANDC__
|
|
|
|
|
+#ifdef __GNUC__
|
|
|
|
|
+ // 以下两个函数都是static,可通过全名调用,一下分配512bytes
|
|
|
|
|
+ void* p4 = alloc::allocate(512);
|
|
|
|
|
+ alloc::deallocate(p4, 512);
|
|
|
|
|
+#endif // __GNUC__
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 上述代码为四种内存分配的基本用法
|
|
|
|
|
+
|
|
|
|
|
+-----
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+class Complex {
|
|
|
|
|
+public:
|
|
|
|
|
+ Complex(int x, int y) {
|
|
|
|
|
+ m_x = x;
|
|
|
|
|
+ m_y = y;
|
|
|
|
|
+ std::cout << "constructor Complex" << std::endl;
|
|
|
|
|
+ }
|
|
|
|
|
+ Complex() {
|
|
|
|
|
+ m_x = 0;
|
|
|
|
|
+ m_y = 0;
|
|
|
|
|
+ std::cout << "constructor Complex" << std::endl;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+private:
|
|
|
|
|
+ int m_x;
|
|
|
|
|
+ int m_y;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+void func_new() {
|
|
|
|
|
+ Complex* p1 = new Complex(1, 2);
|
|
|
|
|
+ delete p1;
|
|
|
|
|
+
|
|
|
|
|
+ // 等价于
|
|
|
|
|
+ Complex* p2;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ void* mem = operator new(sizeof(Complex));
|
|
|
|
|
+ p2 = static_cast<Complex*>(mem);
|
|
|
|
|
+ p2->Complex::Complex(1, 2);
|
|
|
|
|
+ operator delete(p2);
|
|
|
|
|
+ // 这里直接调用构造函数的动作 只有编译器才可以这么写 自己写不同平台可能报错
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (std::bad_alloc)
|
|
|
|
|
+ {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> tip: 上述代码中说明了不可直接`pc->Complex::Complex(1,2)`方法,如果想直接调用构造函数可以运用**placement new**,写法是`new(p)Complex(1,2)`
|
|
|
|
|
+
|
|
|
|
|
+-----
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+// gcc 中 operator new的实现方式
|
|
|
|
|
+void *operator new(std::size_t size) _THROW_BAD_ALLOC
|
|
|
|
|
+{
|
|
|
|
|
+ if (size == 0)
|
|
|
|
|
+ size = 1;
|
|
|
|
|
+ void* p;
|
|
|
|
|
+ while ((p = ::malloc(size)) == nullptr)
|
|
|
|
|
+ {
|
|
|
|
|
+ // If malloc fails and there is a new_handler,
|
|
|
|
|
+ // call it to try free up memory.
|
|
|
|
|
+ std::new_handler nh = std::get_new_handler();
|
|
|
|
|
+ if (nh)
|
|
|
|
|
+ nh();
|
|
|
|
|
+ else
|
|
|
|
|
+#ifndef _LIBCPP_NO_EXCEPTIONS
|
|
|
|
|
+ throw std::bad_alloc();
|
|
|
|
|
+#else
|
|
|
|
|
+ break;
|
|
|
|
|
+#endif
|
|
|
|
|
+ }
|
|
|
|
|
+ return p;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+从上面的代码可以看到,`new`的实现方式就是调用了`operator new`方法,在强转之后执行了对应的构造函数
|
|
|
|
|
+
|
|
|
|
|
+而`operator new`方法中,真正分配内存的是`malloc`方法
|
|
|
|
|
+
|
|
|
|
|
+> [网上看到的资料](https://witgao.com/2021/07/07/c++/libc++%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8Bnew%E5%92%8Cdelete/)
|
|
|
|
|
+
|
|
|
|
|
+-----
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Complex* pc = new Complex(1, 2);
|
|
|
|
|
+// ... some operation
|
|
|
|
|
+delete pc;
|
|
|
|
|
+
|
|
|
|
|
+// 编译器将delete转换成下面操作
|
|
|
|
|
+pc->~Complex(); // 先析构
|
|
|
|
|
+operator delete(pc); // 然后释放内存
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 上述为delete的操作,这里直接调用析构函数不会报错
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+void operator delete(void* ptr) noexcept
|
|
|
|
|
+{
|
|
|
|
|
+ ::free(ptr);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> `operator delete`底层通过`free`释放内存
|
|
|
|
|
+
|
|
|
|
|
+-----
|
|
|
|
|
+
|
|
|
|
|
+上述代码中`pc->Complex::Complex(1, 2)`直接执行构造函数没有报错,并不能表示我们可以都这么直接调用构造,下面代码就是一个反例
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+string* pstr = new string;
|
|
|
|
|
+pstr->string::string();
|
|
|
|
|
+pstr->~string();
|
|
|
|
|
+delete pstr;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+这里的代码会明显报错`class std::basic_string<char> has no member named string`
|
|
|
|
|
+这里报错的原因是`string`本来的名字其实叫`basic_string`,是被`typedef`成`string`,所以`string`的构造函数应该是`basic_string()`才对
|
|
|
|
|
+
|
|
|
|
|
+除了`typedef`导致构造函数名字不对的问题之外,**编译器的严格程度**不同也会出现报错,即存在平台差异性,所以不推荐手动调用构造函数
|
|
|
|
|
+
|
|
|
|
|
+### array new / array delete
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Complex* pca = new Complex[3]; // 唤起三次构造函数
|
|
|
|
|
+// ... some operation
|
|
|
|
|
+delete[] pca; // 唤起三次析构
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 如果写的是`delete`而不是`delete[]`,那么编译器只会执行一次析构函数,而不是三次,也就是说有两个对象的析构函数没有执行,如果对象中存在`new`操作,这样会导致无法执行`delete`进而导致内存泄漏
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+从上图中可以看到,`new`出的内存块除了包含三个`Complex`对象外,还包含一些`cookie`信息,这些信息帮助操作系统释放整块内存,而`delete`与`delete[]`区别仅仅在于数组中所有对象是否都执行析构
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+class A{
|
|
|
|
|
+public:
|
|
|
|
|
+ int id;
|
|
|
|
|
+
|
|
|
|
|
+ A(): id(0) { std::cout << "default " << this << " " << this->id << std::endl; };
|
|
|
|
|
+ A(int i): id(i) { std::cout << this << " " << "int cons" << i << std::endl; }
|
|
|
|
|
+ ~A() { std::cout << " ~ " << this << " " << this->id << std::endl; }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+void test_placement_new() {
|
|
|
|
|
+
|
|
|
|
|
+ A* buf = new A[3]; // 调用A的默认构造函数,创建对象
|
|
|
|
|
+ A* tmp = buf;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < 3; i++) {
|
|
|
|
|
+ new(tmp++)A(i); // placement new的写法,在tmp地址的构建A对象,调用A的构造函数
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ delete[] buf;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+default 00A64CF4 0
|
|
|
|
|
+default 00A64CF8 0
|
|
|
|
|
+default 00A64CFC 0
|
|
|
|
|
+00A64CF4 int cons0
|
|
|
|
|
+00A64CF8 int cons1
|
|
|
|
|
+00A64CFC int cons2
|
|
|
|
|
+~ 00A64CFC 2
|
|
|
|
|
+~ 00A64CF8 1
|
|
|
|
|
+~ 00A64CF4 0
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+通过**查看输出**,可以发现这上述代码的执行顺序
|
|
|
|
|
+
|
|
|
|
|
+### placement new
|
|
|
|
|
+
|
|
|
|
|
+- `placement new` 允许哦我们将object建构于`allocated memory`(已经分配的内存)中
|
|
|
|
|
+- 没有`placement delete`,因为`placement new`根本没有分配内存
|
|
|
|
|
+
|
|
|
|
|
+> 因为是构建于已经分配的内存中,所以需要一个现成的被分配的内存空间的指针
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+#include <new>
|
|
|
|
|
+
|
|
|
|
|
+void func(){
|
|
|
|
|
+ char* buf = new char[sizeof(Complex) * 3];
|
|
|
|
|
+ Complex* pc = new(buf)Complex(1, 2);
|
|
|
|
|
+ // ....
|
|
|
|
|
+ delete[] buf;
|
|
|
|
|
+}
|
|
|
|
|
+void func_1(){
|
|
|
|
|
+ Complex* pc = new(buf)Complex(1, 2);
|
|
|
|
|
+ // 上面一行代码 等价下面的代码
|
|
|
|
|
+
|
|
|
|
|
+ Complex* pc;
|
|
|
|
|
+ try{
|
|
|
|
|
+ void* mem = operator new(sizeof(Complex), buf);
|
|
|
|
|
+ pc = static_cast<Complex*>(mem);
|
|
|
|
|
+ pc->Complex::Complex(1, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch(std::bad_alloc){
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 下面为当 operator new的两个参数为size_t和void*的源码
|
|
|
|
|
+void* operator new(size_t, void* loc){
|
|
|
|
|
+ return loc;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+分析上述代码可以发现
|
|
|
|
|
+
|
|
|
|
|
+1. `operator new(size_t, void* loc)`什么都没做,因为此时`loc`是已经分配好了的内存地址,所以不需要再分配内存
|
|
|
|
|
+2. `placement new`其实就是执行了一次构造函数
|
|
|
|
|
+
|
|
|
|
|
+### C++分配内存的途径
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+一般来说,我们会重载类的`Foo::operator new`和`Foo::operator delete`,将对象的创建和内存分配自己来控制(通过自己控制可以减少内存块上的cookie的使用)
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Foo*p = (Foo*)malloc(sizeof(Foo));
|
|
|
|
|
+new(p)Foo(x);
|
|
|
|
|
+// ...
|
|
|
|
|
+p->~Foo();
|
|
|
|
|
+free(p);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+当然也可以重载全局`::operator new / ::operator delete`
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+// 测试重写代码
|
|
|
|
|
+
|
|
|
|
|
+void* myAlloc(size_t size){
|
|
|
|
|
+ return malloc(size);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void myFree(void* ptr){
|
|
|
|
|
+ return free(ptr);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline void* operator new(size_t size){
|
|
|
|
|
+ cout << "my new" << std::endl;
|
|
|
|
|
+ return myAlloc(size);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline void* operator new[](size_t size){
|
|
|
|
|
+ cout << "my new[]" << std::endl;
|
|
|
|
|
+ return myAlloc(size);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline void operator delete(void* ptr){
|
|
|
|
|
+ cout << "my delete" << std::endl;
|
|
|
|
|
+ myFree(ptr);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline void operator delete[](void* ptr){
|
|
|
|
|
+ cout << "my delete[]" << std::endl;
|
|
|
|
|
+ myFree(ptr);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+更常见的写法是**类中重载**
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+class Foo{
|
|
|
|
|
+public:
|
|
|
|
|
+ void* operator new(size_t size);
|
|
|
|
|
+ void operator delete(void*[,size_t]); // 第二个参数size_t可有可无,写不写都行
|
|
|
|
|
+ void* operator new[](size_t size);
|
|
|
|
|
+ void operator delete[](void*[, size_t]);
|
|
|
|
|
+}
|
|
|
|
|
+```
|