C语言动态内存管理
动态内存管理
malloc 、calloc 、realloc 、free 函数
想必不用多说
关于区域划分
陈年旧题:
C++动态内存管理
//int* tmp = (int*)malloc(sizeof(int));
int* tmp = (int*)malloc(sizeof(int)* 10);
if(tmp == NULL){
perror("malloc fail")
}
else{
p1 = tmp;
}
free(p1);
//int* p2 = new int;
//delete p2;
int* p2 = new int[10];
delete[] p2;// []
不同于 malloc 、free 是函数调用,new 、delete 是操作符
优点
- 简化使用:new 无需强制类型转换,无需自己计算类型大小,不需要检查是否成功开辟空间(不过需要捕获异常)
- 为自定义类型量身定做(对于自定义类型,malloc 只开空间,而 new 会开空间并调用构造函数进行初始化,相应的,delete 会释放空间并调用析构函数)
C语言想要创建节点:
struct ListNode{ //typedef 退出舞台
int val;
ListNode* next;
};
ListNode* BuyNode(int data){
//…………………………………………………………
}
C++:一步到位
struct ListNode{
int _val;
ListNode* _next;
ListNode(int data)//构造函数
:_val(data)
,_next(nullptr)
{}
};
int main(){
ListNode* n1 = new ListNode(1);//new 后面可以传参
ListNode* n2 = new ListNode(2);
}
- 可以以某种形式主动让其进行初始化
int* p1 = new int(10);//申请一个 int,初始化为 10
int* p2 = new int[10] {1, 2, 3, 4};//开辟的空间{1,2,3,4,0,0,0,0,0,0}
不足
new 和 malloc 等一样,不会对开辟的空间进行初始化
注意
malloc – free 、new – delete 、new …[] – delete[] 匹配使用,不要闲的没事交叉乱用(不然可能出现奇奇怪怪的问题)
operator new 和 operator delete 函数
首先,这两个不是重载而是系统提供的全局函数(奈何这起名诱导性太强)
● new 的原理:
- 在底层通过调用 operator new 申请空间;
- 在申请的空间上执行构造函数
● delete 的原理:
- 在空间上执行析构函数
- 调用 operator delete 释放空间;
● new T[N] 的原理:
- 调用 operator new[] 函数(是的,还有这么一种函数),在operator new[] 中实际调用 operator new 完成对 N 个对象的申请
- 在申请的空间进行 N 次构造函数
● delete[] 的原理:
- 在空间上执行 N 次析构函数
- 调用 operator delete[] 函数释放空间,实际在 operator delete[] 函数中调用 operator delete
既如此,我们也可以直接使用 operator new 申请空间(了解即可):
int* p1 = (int*)operator new(sizeof(int*));//开辟失败抛异常
int* p2 = (int*)malloc(sizeof(int*));//开辟失败返回 nullptr
//检查 …………………………………………
这两个函数(operator new 和 operator delete)本质上是对 malloc 和 free 的封装,并设计成出问题后可以抛异常
再次了解 new delete 为自定义类型定做的含义
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = new int[4];
_top = 0;
_capacity = 4;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main() {
Stack* pst = new Stack;//new 先调用 operator new 开空间,后调用构造函数
delete pst;//delete 先调用析构函数,后调用 operator delete 释放空间
如果错误地使用 free pst; 的话,尽管不会报错,但分析后得知会存在 内存泄漏
}
关于错误匹配导致的报错(了解即可)
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A 构造函数:" << this << endl;
}
~A()
{
cout << "A 析构函数:" << this << endl;
}
private:
int _a;
};
int main() {
A* p = new A[10];
delete[] p; //此处只有这样匹配才能正常运行
return 0;
}
只有用 delete[] 才能正常释放空间 ——
VS编译器存在的机制:new 时已知要调用 10 次构造函数,为了让 delete 知道要调用 10 次析构,在开空间时还会在起始处额外开辟空间(4个字节)记录该个数
存储的数据是需要调用析构函数的次数
既然以上在起始位置开辟的 4 个字节是为了让 delete 知道要调用 10 次析构,那么当我们将上述代码中实现的析构函数删去后将发现
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A 构造函数:" << this << endl;
}
private:
int _a;
};
int main() {
A* p = new A[10];
delete p;//不再报错
return 0;
}
delete p 不再报错
原因:编译器判断默认的析构函数没有调用的必要,干脆就不调用了,既然无需调用析构,也就无需特定开空间记录调用次数,所以释放空间的位置没有问题,不会报错
关于捕获异常
因为异常涉及到继承和多态,所以在此不详细讲解异常
使用 malloc 查看能开多大空间:
int main()
{
size_t size = 0;
while (1)
{
//每次申请一定大小的空间试探(此处为 1M)
int* p1 = (int*)malloc(1024 * 1024 * 4);
//看是否返回空指针判断空间是否开辟成功
if (p1 == nullptr)
{
break;
}
size += 1024 * 1024 * 4;
cout << p1 << endl;
}
cout << size << endl;
cout << size / 1024 / 1024 << "MB" << endl;//发现开了约 2G
return 0;
}
使用 new:
int main()
{
size_t size = 0;
try
{
while (1)
{
int* p1 = new int[1024 * 1024];
size += 1024 * 1024 * 4;
cout << p1 << endl;
}
}
catch (const exception& e)
{
cout << e.what() << endl;
}
cout << size << endl;
cout << size / 1024 / 1024 << "mb" << endl;
return 0;
}
定位new
可对已有的空间进行初始化
示例:
int main()
{
A aa; //正常调用构造函数
A* p1 = (A*)malloc(sizeof(A));//不会调用构造函数
if (p1 == nullptr)
{
perror("malloc fail");
}
//构造函数不能显示调用,这里想要初始化就要用到 定位new
new(p1)A(1);//new(指针)构造函数
//析构函数可以显示调用
p1->~A();
free(p1);
return 0;
}
以上示例中,显然本来 new 一下就能解决的事情反而写的复杂又难用 —— 但确实有应用到的场景:
我们申请内存一般都是找 操作系统 申请,但有时为了提升性能,会向 内存池 申请(向操作系统和内存池申请空间的情况可以类比成吃饭顿顿向家长汇报花费申请饭钱和一次申请后家长把一定数额的钱打银行卡里,直到某次吃饭钱不够了再打)
从内存池获取的内存没有初始化,需要用到定位new