Ver código fonte

CPP内存机制的基础入门

usuiforhe 3 anos atrás
pai
commit
acd58f41b4
4 arquivos alterados com 327 adições e 0 exclusões
  1. BIN
      cpp/img/Memory_1.png
  2. BIN
      cpp/img/Memory_2.png
  3. BIN
      cpp/img/Memory_3.png
  4. 327 0
      cpp/内存机制.md

BIN
cpp/img/Memory_1.png


BIN
cpp/img/Memory_2.png


BIN
cpp/img/Memory_3.png


+ 327 - 0
cpp/内存机制.md

@@ -0,0 +1,327 @@
+# 内存管理
+
+![C++应用程序](./img/Memory_1.png)
+
+## 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`进而导致内存泄漏
+
+![C++应用程序](./img/Memory_2.png)
+
+从上图中可以看到,`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++分配内存的途径
+
+![C++应用程序](./img/Memory_3.png)
+
+一般来说,我们会重载类的`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]);
+}
+```