目录
3. operator new与operator delete
?????????C++作为一种面向对象的编程语言,继承了C语言的内存管理特性,同时也引入了更加灵活和高级的内存管理机制。在C++中,内存管理涉及到动态内存分配、内存释放、内存泄漏等问题,对于程序的性能和稳定性都有着重要的影响。本文将详细介绍C++中的内存管理。
我们先来了解一下C/C++中内存区域的划分:
栈:栈是用来存放局部变量和函数调用信息的地方。当一个函数被调用时,它的参数和局部变量会被压入栈中,当函数执行完毕时,这些数据会被自动弹出。栈的大小是有限的,通常在几兆字节到几十兆字节之间
堆:堆是用来存放动态分配的内存的地方。在堆中分配的内存需要手动释放,否则会导致内存泄漏。堆的大小通常受系统总内存的限制,可以动态扩展
数据段:这个区域用来存放全局变量和静态变量。全局变量在程序整个运行周期内都存在,而静态变量在它们所在的函数执行期间存在,但是在程序整个运行周期内都存在
代码段:这个区域存放程序的代码和常量数据,如字符串常量。这部分内存通常是只读的
?目前的学习了解这几个即可。
????????C++继承了C语言的内存管理特性,C语言内存管理方式在C++中可以继续使用,但在C++一些使用场景下C的内存管理使用时又有些比较麻烦。于是C++引入了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
?使用示例:
int main()
{
// 动态申请一个int类型的空间
int* p1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* p2 = new int(10);
// 动态申请10个int类型的空间
int* p3 = new int[3];
delete p1;
delete p2;
delete[] p3;
}
?不可malloc开空间,delete释放或者new开空间,free释放
比如:使用 new 开空间,delete [ ] 释放,new ?[ ] 开空间,delete 释放 (不可)
?与C语言中malloc和free类似,使用new来申请一块内存空间,那么必须使用delete来释放整块内存
?以一个简单的栈为例:
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = new int[capacity];
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack* p1 = new Stack;
delete p1;
}
?new/delete对于? 自定义类型? 除了开空间还会调用构造函数和析构函数
Stack* p1 = new Stack; //主要干两件事:开空间 + 调构造
delete p1; // 析构 + 释放空间
?看到operator或许你已经有了猜想,new和delete其实就是操作符
?????????new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,里边封装了malloc和free,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
Stack* p1 = new Stack;
delete p1;
Stack* p2 = (Stack*)operator new(sizeof(Stack));
operator delete(p2);
?在调用时new 和 operator new(delete 和 operator delete)其实是不一样的,通过两种方式的输出结果就可以看出来。
只有new 和 delete 调用了 构造 和 析构。
operator new 并不具备初始化的作用,它存在的意义:new对自定义类型是开空间 和 调构造,而operator new用于new的开空间操作。
?operator delete 只有释放空间的作用,并不会调用析构函数 他的意义是为了与new 配对。
?开单个对象的空间我们了解了,那开多个对象的空间是怎么开的?
?operator new [ ] 的底层其实调用的是operator new,实际调用的关系:
?operator new [ ] -->?operator new --> malloc
operator new [ ]在函数内计算出要开的字节大小,然后使用operator new 去开空间:
?32位环境下,开10个stack对象的空间,size应该是120,可为什么是124?
这多出的4个字节开在开头位置:
?
?这多开出的4个字节空间存储的其实是new [ ]中的值(申请Stack对象的个数)。
????????这个空间的数据就是申请空间Stack对象的个数,例子中我开10个Stack对象空间,所以存储的是10.
????????这个10实际上是为delete [ ]? p3 准备的,在调用 delete [ ]时调用10次析构函数,释放每个对象_a的空间;
delete [ ] 释放空间时会连同这4个字节一起释放(让指针回到new [ ]所开空间最开始的位置)这也就是为什么建议使用 new 和 delete 时配合使用。
因为使用new [ ] 开空间,一旦自定义类型的对象涉及到内存申请,使用delete就会导致释放的空间不完整(delete会导致最开始的4字节空间不被释放),程序就会挂掉。
?建议: new / delete、new [ ] / delete [ ]、malloc / free 一定要配对使用
?在了解定位new之前,先思考一个问题,构造函数能不能显示调用?
?答案是不能,下边是测试的代码,可以自己测试一下。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)operator new(sizeof(A));
//p1->~A(1);
return 0;
}
?虽然构造函数没法正常的进行显示调用,但是我们可以使用定位new来显示调用构造函数
new(p1)A; //new(p1)A(1);
?定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列
?构造函数不能直接显示调用,析构函数可以直接显示调用
p1->~A(); //调用析构
operator delete(p1); // 释放空间
?总的来说:
// new 效果
A* p1 = (A*)operator new(sizeof(A));
new(p1)A;
// delete 效果
p1->~A();
operator delete(p1);
?应用场景:
那定位new有什么用?直接用 new 和 delete 不是更简洁
?定位new表达式在实际中一般是配合内存池使用
?在一些应用场景中,可能涉及到频繁的new申请空间,这样效率很低,为了解决效率问题,就有了池化技术——内存池
频繁的申请空间麻烦,那就一次多申请一些空间,使用时调用构造函数进行初始化。
?这时调用构造函数就需要使用定位new。
?共同点:
?不同点:
?????????内存管理是C++编程中一个重要且复杂的主题,合理地使用new和delete可以避免内存泄漏和提高程序的性能。同时,了解定位new的使用场景也能让我们更好地控制内存分配和对象构造的过程。以上便是本文全部内容,希望对你有所帮助,感谢阅读!