本系列暴死,不会更新了。
该系列为本人的学习笔记,主要由本人整理书写而成。部分内容来自教材、视频课程等,不能保证完全原创性。
萌新的学习笔记,写错了恳请斧正。
# C 语言中的动态内存管理
C 语言中,我们使用 malloc 、 calloc 、 realloc 、 free 来动态管理内存。
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++ 中有更简单方便的内存管理方式,那就是使用 new 、 delete 来管理内存。
# 使用 new/delete 操作内置类型
注意:如果使用 new 开辟空间就用 delete 删除,如果使用 new [] 开辟数组就用 delete [] 删除。
动态申请与删除一个内置类型变量的空间:
int* pi = new int;
int* pf = new float;
int* pl = new long;
int* pb = new bool;
delete pi;
delete pf;
delete pl;
delete pb;
动态申请与删除一个内置类型变量的空间并完成初始化:
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;
动态申请与删除内置类型变量数组的空间:
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;
动态申请与删除内置类型变量数组的空间并完成初始化:
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 基本类似。
不同的是:
- new/delete 申请和释放的是单个元素的空间,new [] 和 delete [] 申请的是连续空间。
- new 在申请空间失败时会抛异常;malloc 会返回 NULL。
对于自定义类型:
- new 的原理:
- 调用 operator new 函数申请空间。
- 执行构造函数。
- delete 的原理:
- 执行析构函数。
- 调用 operator delete 函数释放空间。
- new [] 的原理:
- 调用 operator new [] 函数申请空间。
- 分别执行构造函数。
- 在这片空间前紧挨的位置申请 4 个字节用于保存 N。
- 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. |