目录
前言
C/C++内存分布
C++内存管理方式
1. new和delete操作内置类型
快速了解与使用
2. new和delete操作自定义类型
3. operator new与operator delete
4. operator new [ ]
*5.定位new
6. malloc/free和new/delete的区别
总结
前言
C++作为一种面向对象的编程语言,继承了C语言的内存管理特性,同时也引入了更加灵活和高级的内存管理机制。在C++中,内存管理涉及到动态内存分配、内存释放、内存泄漏等问题,对于程序的性能和稳定性都有着重要的影响。本文将详细介绍C++中的内存管理。
C/C++内存分布
我们先来了解一下C/C++中内存区域的划分:
栈:栈是用来存放局部变量和函数调用信息的地方。当一个函数被调用时,它的参数和局部变量会被压入栈中,当函数执行完毕时,这些数据会被自动弹出。栈的大小是有限的,通常在几兆字节到几十兆字节之间
堆:堆是用来存放动态分配的内存的地方。在堆中分配的内存需要手动释放,否则会导致内存泄漏。堆的大小通常受系统总内存的限制,可以动态扩展
数据段:这个区域用来存放全局变量和静态变量。全局变量在程序整个运行周期内都存在,而静态变量在它们所在的函数执行期间存在,但是在程序整个运行周期内都存在
代码段:这个区域存放程序的代码和常量数据,如字符串常量。这部分内存通常是只读的
目前的学习了解这几个即可。
C++内存管理方式
1. new和delete操作内置类型
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;
}
- new和delete要配合使用
不可malloc开空间,delete释放或者new开空间,free释放
- new和delete使用时类型要相符
比如:使用 new 开空间,delete [ ] 释放,new [ ] 开空间,delete 释放 (不可)
- new 和delete申请和释放空间必须是完整连续的
与C语言中malloc和free类似,使用new来申请一块内存空间,那么必须使用delete来释放整块内存
2. 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; //主要干两件事:开空间 + 调构造
- 先给Stack 指针 p1开块空间
- 调用构造函数给p指向的空间进行初始化
delete p1; // 析构 + 释放空间
- 先释放_a的空间(先释放p1空间会导致_a指向空间丢失)
- 释放p1指向的空间
3. operator new与operator delete
看到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 配对。
4. operator new [ ]
开单个对象的空间我们了解了,那开多个对象的空间是怎么开的?
- 一次申请10个Stack对象的空间
- 调用10次构造函数(给每个对象的_a开空间)
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 一定要配对使用
*5.定位new
在了解定位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。
6. malloc/free和new/delete的区别
共同点:
- 都是从堆上申请空间,并且需要用户手动释放
不同点:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,new可以捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
总结
内存管理是C++编程中一个重要且复杂的主题,合理地使用new和delete可以避免内存泄漏和提高程序的性能。同时,了解定位new的使用场景也能让我们更好地控制内存分配和对象构造的过程。以上便是本文全部内容,希望对你有所帮助,感谢阅读!