文章目录
- [1. C/C++内存分布](#1. C/C++内存分布)
- [2. C++中的内存管理方式](#2. C++中的内存管理方式)
- [3. operator new和operator delete函数](#3. operator new和operator delete函数)
- [4. new和delete的实现原理](#4. new和delete的实现原理)
- [5. 定位new表达式(placement-new)](#5. 定位new表达式(placement-new))
1. C/C++内存分布
C语言中,为了方便管理内存空间,将内存分成了不同的区域,每个区域管理不同的数据
C++中,保持着跟C语言一样的内存区域划分
C++
int main()
{
int a = 0;
const int b = 1;
//很多人有个误区,认为被const修饰的变量在常量区
//实际上,被const修饰的变量叫做常变量,它本质还是个变量,只不过有了常属性
//因此b是在栈区上的
cout << &a << endl;
cout << &b << endl;
char arr1[] = "abcd";
const char* arr2 = "abcd";
arr1[0]++;
//arr2[0]++;
((char*)arr2)[0]++;
//即使将arr2强转成char*,程序在运行的过程中也会崩溃,暴力修改常量区的数据
return 0;
}
2. C++中的内存管理方式
C++语言中,我们用malloc/calloc/realloc/free函数进行内存的开辟和释放,对内存进行管理
C++中,这些函数依然可用,但在使用上是比较麻烦的;因此C++中增加了new和delete操作符,使用这两个操作符进行内存的管理
- C语言使用的是函数进行内存管理
- C++中new和delete是操作符,不是函数
C++
int main()
{
//申请一个int类型的空间
int* p1 = new int;
//申请一个int类型的空间并初始化
int* p2 = new int(1);
//申请一个数组
int* p3 = new int[10];
//C++11支持
//申请一个数据并初始化
int* p4 = new int[10]{ 1,2,3 };
delete p1;
delete p2;
delete[] p3;
delete[] p4;
return 0;
}
相比于C语言中利用函数管理内存,C++中的管理内存方式有哪些好处:
用法更加简洁
可以控制初始化
对于自定义类型,开辟空间+自动调用对应的构造函数
C++struct ListNode { public: ListNode(int val) :_val(val) , _next(nullptr) {} int _val; ListNode* _next; }; int main() { ListNode* node1 = new ListNode(1); ListNode* node2 = new ListNode(2); ListNode* node3 = new ListNode(3); delete node1; delete node2; delete node3; return 0; }
new失败了以后抛异常,不需要手动检查
C++void Func2() { int n = 1; while (1) { int* p = new int[1024 * 1024 * 10]; cout << n << "->" << p << endl; n++; } } int main() { try { Func2(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
对于自定义类型,申请空间时,new会调用对应的构造函数;delete会调用对应的析构函数
C++class A { public: A(int a = 0) :_a(a) { cout << "A(int a)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; };
C++int main() { A* p1 = new A(1);//调用一次构造 delete p1;//调用一次析构 A* p2 = new A[10];//调用十次构造 delete[] p2;//调用十次析构 return 0; }
3. operator new和operator delete函数
看到operator+操作符,第一直觉会以为这是对new和delete操作符的重载函数,但其实它们不是重载函数
实际上,这两个函数是系统提供的全局函数
在底层,new通过调用operator new函数来实现申请空间;delete通过调用operator delete函数来时间空间的释放
operator new函数最终通过malloc函数申请空间,申请成功则返回,否则抛异常
operator delete函数最终通过free函数释放空间
可以看到,new和delete的最底层还是使用的malloc和free函数,只不过是对它进行了一系列封装
4. new和delete的实现原理
对于内置类型:
- 两者的效果差不多,不同的是new在申请空间失败时抛异常,malloc则返回NULL
对于自定义类型:
- new会调用operator new函数申请空间,再在申请的空间上调用构造函数
- delete会调用析构函数完成对象中的资源清理,再调用operator delete释放对象的空间
- new T[n]会调用operator new[]函数,operator new[]函数会调用operator new函数申请n个对象的空间
- delete[]会调用n次析构函数,完成n个对象中资源的清理,再调用operator delete[]函数,operator delete[]安徽念书会调用operator delete函数,释放n个对象的空间
C++
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p3 = new A[10];
delete[] p3;
return 0;
}
当然,不是所有情况都会留出一块空间存放对象个数
内置类型不需要析构,也就不会留出空间
没有显示定义析构函数的自定义类型也不会留出空间,因为没有显示定义析构函数,就认为不需要析构函数释放对象中的资源
new和delete使用时一定要对应,否则可能会出现不确定的结果
C++
int main()
{
// 程序能运行,但不推荐
int* p1 = new int(1);
delete[] p1;//error 正确的写法:delete p1
// 若A显示定义析构函数,程序陷入无限调用析构的死循环中
// 若A没有显示定义析构函数,程序能运行,但不推荐
A* p2 = new A(1);
delete[] p2;//error 正确的写法:delete p2
// 程序能运行,但不推荐
int* p3 = new int[10];
delete p3;//error 正确的写法:delete[] p3
// 若A显示定义析构函数,程序报错,原因是释放的位置错了
// 若A没有显示定义析构函数,程序能运行,但不推荐
A* p4 = new A[10];
delete p4;//error 正确的写法:delete[] p4
return 0;
}
C++
// C语言创建不带哨兵位的链表
typedef int DataType;
struct ListNode
{
DataType val;
struct ListNode* next;
};
struct ListNode* CreateNewNode(DataType val)
{
struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newNode == NULL)
{
perror("malloc fail");
exit(-1);
}
newNode->next = NULL;
newNode->val = val;
return newNode;
}
struct ListNode* CreateList(int n)
{
struct ListNode* head = CreateNewNode(-1);
struct ListNode* tail = head;
for (int i = 1; i <= n; i++)
{
struct ListNode* temp = CreateNewNode(i);
tail->next = temp;
tail = tail->next;
}
struct ListNode* cur = head->next;
free(head);
return cur;
}
int main()
{
struct ListNode* head = CreateList(5);
return 0;
}
struct ListNode
{
public:
ListNode(int val)
:_val(val)
,_next(nullptr)
{}
int _val;
ListNode* _next;
};
// C++创建不带哨兵位的链表
ListNode* CreateList(int n)
{
ListNode head(-1);
ListNode* tail = &head;
int val = 0;
printf("请依次输入值:>");
for (int i = 1; i <= n; i++)
{
cin >> val;
ListNode* newNode = new ListNode(val);
tail->_next = newNode;
tail = tail->_next;
}
return head._next;
}
int main()
{
ListNode* list = CreateList(5);
return 0;
}
5. 定位new表达式(placement-new)
在某些场景下,我们向内存申请的空间没有初始化,比如向内存池申请的空间,如果是自定义类型的对象,我们可以使用new的定义表达式进行显示调用构造函数进行初始化
C++
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)operator new(sizeof(A));
new(p1)A(1);
p1->~A();
operator delete(p1);
return 0;
}
malloc/free和new/delete的区别:
共同点:都是从堆上申请空间,需要自己手动释放空间
不同点:
用法上:
- malloc/free是函数;new/delete是操作符
- malloc申请的空间不会初始化;new会初始化
- malloc申请空间时,需要手动计算空间大小;new只需加上空间类型即可,想创建多个对象,只需在[]里加上对象的个数
- malloc的返回值为void*,需要做强转处理;new的返回值就是空间类型,不需要处理
- malloc申请空间失败时返回NULL,因此使用时必须先判空;new不需要,但new需要捕获异常
底层特性上: