本系列暴死,不会更新了。

该系列为本人的学习笔记,主要由本人整理书写而成。部分内容来自教材、视频课程等,不能保证完全原创性。

萌新的学习笔记,写错了恳请斧正。

# C 语言中的动态内存管理

C 语言中,我们使用 malloccallocreallocfree 来动态管理内存。

int main()
{
    int* p = (int*)malloc(sizeof(int));	// 动态开辟
    free(p);
    int* q = (int*)calloc(4, sizeof(int));	// 动态开辟并赋值
    int* r = (int*)realloc(q, 10 * sizeof(int));	// 重新分配空间
    free(r);
}

# C++ 中的动态内存管理

C++ 中有更简单方便的内存管理方式,那就是使用 newdelete 来管理内存。

# 使用 new/delete 操作内置类型

注意:如果使用 new 开辟空间就用 delete 删除,如果使用 new [] 开辟数组就用 delete [] 删除。

  1. 动态申请与删除一个内置类型变量的空间:

    int* pi = new int;
    int* pf = new float;
    int* pl = new long;
    int* pb = new bool;
    delete pi;
    delete pf;
    delete pl;
    delete pb;
  2. 动态申请与删除一个内置类型变量的空间并完成初始化:

    int* pi = new int(114514);
    int* pf = new float(3.14f);
    int* pl = new long(1145141919810);
    int* pb = new bool(true);
    delete pi;
    delete pf;
    delete pl;
    delete pb;
  3. 动态申请与删除内置类型变量数组的空间:

    int* pi = new int[5];
    int* pf = new float[5];
    int* pl = new long[5];
    int* pb = new bool[5];
    delete[] pi;
    delete[] pf;
    delete[] pl;
    delete[] pb;
  4. 动态申请与删除内置类型变量数组的空间并完成初始化:

    int* pi = new int[5]{1, 2, 3, 4, 5};
    float* pf = new float[5]{1.1f, 2.2f, 3.3f, 4.4f, 5.5f};
    long* pl = new long[5]{10, 20, 30, 40, 50};
    bool* pb = new bool[5]{true, false, true, false, true};
    delete[] pi;
    delete[] pf;
    delete[] pl;
    delete[] pb;

# 使用 new/delete 操作自定义类型

这与自定义类型区别主要在于:使用 new/delete 操作自定义类型时会自动调用自定义类型的构造 / 析构函数。

class A
{
public:
    A(int a = 0)
        : m_a(a)
    {
        cout << "A" << endl;
    }
    
    ~A()
    {
        cout << "~A" << endl;
    }
    
private:
    int m_a;
};
int main()
{
    A* oA = new A;
    delete oA;
    A* oB = (A*)malloc(sizeof(A));
    free(oB);
    
    return 0;
}

该程序运行结果为:

A
~A

# operator new (operator new []) 与 operator delete (operator delete []) 函数

看到 operator new 和 operator delete,很多人会以为这是 new 与 delete 的重载函数。

但是,operator new 和 operator delete 不能理解为是 new 与 delete 的重载函数!

operator new 与 operator delete 其实是系统提供的全局函数,而 new 和 delete 在底层其实就是调用了这两个函数来实现的。

以下给出的函数定义随编译器不同有所变化!

# operator new 函数
void* operator new(std::size_t size) 
{
    if (size == 0)	// 如果请求的大小为 0,分配至少一个字节
    {
        size = 1;
    }
    while (true)
    {
        void* p = std::malloc(size);  // 使用 malloc 分配内存
        if (p) {
            return p;  // 如果分配成功,返回指针
        }
        std::new_handler handler = std::get_new_handler();  // 获取当前的 new_handler
        if (!handler) {
            throw std::bad_alloc();  // 如果没有设置 new_handler,抛出 bad_alloc 异常
        }
        handler();  // 调用 new_handler
    }
}
# operator delete 函数
void operator delete(void* ptr) noexcept	//noexcept 说明此函数不会抛出异常
{
    std::free(ptr);  // 使用 free 释放内存
}
# operator new [] 和 operator delete []

同样的,new [] 和 delete [] 在底层调用的是 operator new [] 和 operator delete [] 函数。

void* operator new[](std::size_t size) 
{
    if (size == 0) 
    {
        size = 1;
    }
    while (true) 
    {
        void* p = std::malloc(size);
        if (p) 
        {
            return p;
        }
        std::new_handler handler = std::get_new_handler();
        if (!handler) 
        {
            throw std::bad_alloc();
        }
        handler();
    }
}
void operator delete[](void* ptr) noexcept 
{
    std::free(ptr);
}

# new 与 delete 的实现原理

如果申请的是内置类型的空间,new 和 malloc、delete 和 free 基本类似。

不同的是:

  1. new/delete 申请和释放的是单个元素的空间,new [] 和 delete [] 申请的是连续空间
  2. new 在申请空间失败时会抛异常;malloc 会返回 NULL。

对于自定义类型:

  1. new 的原理:
    • 调用 operator new 函数申请空间。
    • 执行构造函数。
  2. delete 的原理:
    • 执行析构函数。
    • 调用 operator delete 函数释放空间。
  3. new [] 的原理:
    • 调用 operator new [] 函数申请空间。
    • 分别执行构造函数。
    • 在这片空间前紧挨的位置申请 4 个字节用于保存 N。
  4. delete [] 的原理:
    • 往前读取 4 个字节,获得 N。
    • N 个元素分别执行析构函数。
    • 调用 operator delete [] 函数释放空间。

# 定位 new 表达式(placement-new)

我们可以直接通过指针显式的调用析构函数:

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: m_a(a)
	{
		cout << "A()" << this << endl;
	}
	~A()
	{
		cout << "~A()" << this << endl;
	}
private:
	int m_a;
};
int main()
{
	A* p1 = new A;
	p1->~A();
	free(p1);
	return 0;
}

那我们能直接显式调用构造函数吗?答案是否定的。

但是我们可以使用定位 new 表达式

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: m_a(a)
	{
		cout << "A()" << this << endl;
	}
	~A()
	{
		cout << "~A()" << this << endl;
	}
private:
	int m_a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	new (p1) A;	// 在地址 p1 处执行类 A 的构造函数
	p1->~A();
	free(p1);
	return 0;
}

其结构就是 new 加上 (这里放地址) 加上 类名

或者也可以同时初始化:

int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	new (p1) A (10);	// 在地址 p1 处执行类 A 的构造函数并初始化为 10
	p1->~A();
	free(p1);
	return 0;
}

当然我们也能显示的调用 operator new (operator new []) 与 operator delete (operator delete []) 函数:

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: m_a(a)
	{
		cout << "A()" << this << endl;
	}
	~A()
	{
		cout << "~A()" << this << endl;
	}
private:
	int m_a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	new (p1) A;
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new (p2) A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

# 检测内存泄露

在 Visual Stodio 中我们可以使用 _CrtDumpMemoryLeaks() 函数来检测内存泄露,如果发生内存泄露这个函数会在输出窗口给出提示。

#include <iostream>
using namespace std;
int main()
{
	int* p = new int;
	_CrtDumpMemoryLeaks();
	return 0;
}

执行这段代码,我们可以在来源为调试的输出看到如下内容:

Detected memory leaks!
Dumping objects ->
{81} normal block at 0x00000221287C7400, 4 bytes long.
 Data: <    > CD CD CD CD 
Object dump complete.