C++内存管理
C++内存管理
目录
内存模型
内存类型 | 作用 | 生命周期 |
---|---|---|
常量存储区 | 存放常量,不允许修改 | |
全局/静态存储区 | 全局变量和静态 static 对象、类static 数据成员 |
由编译器自动创建和销毁,static 对象在使用之前分配,在程序结束时销毁。 |
自由存储区 | 由 malloc 分配的内存块,free 释放 |
|
栈 | 函数内局部变量 | 函数执行结束时这些存储单元自动被释放。 |
堆 | 用来动态分配的内存,由new 创建分配,delete 释放 |
动态对象的生命周期由应用程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显示地销毁他们。 |
堆与栈的区别
管理方式不同;
- 栈:有系统、编译器自动分配
- 堆:需要程序员自己申请,程序员释放,容易内存泄露
空间大小不同;
对于32位系统而言
- 栈:最大只有2M
- 堆:最大可以到4GB,没有大小限制
分配效率不同;
- 栈:由系统分配,速度较快,程序员无法控制
- 堆:速度慢,容易产生内存碎片
生长方向不同;
- 栈:生长方向向下,内存地址减小
- 堆:生长方向向上,内存地址增加
分配方式不同;
- 栈:栈有2种分配方式:静态分配和动态分配
- 堆:都是动态分配的,没有静态分配的堆。
能否产生碎片不同;
- 栈:栈是先进后出的队列
- 堆:频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片
其他说明
栈:从高地址往低地址增长,存放的是非静态局部变量、函数参数以及返回值等具有临时性的值;动态内存的维护都是要用指针保存地址的,栈就是保存指针的地方,因此栈的容量很小,比如VS编译器给栈分配的大小是1M,一些LInux中能达到8M;栈是系统自动维护的。
内存映射段:装载共享动态内存库,用户可使用系统接口创建共享内存,用作进程间通信,是高效的I/O映射方式。这一部分后续会学习;
堆:从低地址往高地址增长,用于程序运行时动态内存的分配,栈存放的指针维护的空间就在堆,所以堆占的空间比较大,一般有几个G,需要用户自己维护;
数据段:存放着全局数据和静态数据,这部分和栈对应,是存放着具有常性的值;
代码段:可执行的代码、只读常量。
new和delete
C++提出了新的内存管理方式:定义新的操作符new和delete进行动态内存管理。
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间
int* ptr3 = new int[10];
delete ptr1;
delete ptr2;
delete[] ptr3;
解释:
ptr1: 指向的是一个动态内存分配的、未初始化的int类型对象;
ptr2: 指向的是一个值为10,类型是int的对象;
ptr3: 指向的是一个大小为10个int,也就是40(32位)个字节、未初始化的对象;
delete:如果有申请指定大小的空间,需要使用[]。
#include <iostream>
using namespace std;
class MyClass
{
public:
MyClass():i(2){} // 无参 初始化
MyClass(int a):i(a){} // 有参 初始化
virtual void foo(){ cout<<i<<endl;} //
int i;
};
int main()
{
MyClass *p1 = new MyClass;
p1->foo();
MyClass *p2 = new MyClass(10);
p2->foo();
MyClass *p3 = new MyClass[5]; // 创建 5个MyClass对象,同时默认赋值
//MyClass *p4 = new MyClass[5](20); // 错误写法
p3->foo();
delete p1;
delete p2;
delete[] p3;
return 0;
}
// -----------------------------------------------------------------------
2
10
2
6855584
200
malloc/free和new/delete区别
new
关键字是C++的一部分是操作符,malloc
是由C库提供的函数new
以具体类型为单位进行内存分配,malloc
以字节为单位进行内存分配,需要手动计算空间大小并传递new
在申请单个类型变量时可进行初始化。malloc
不具备内存初始化的特性new
返回的是空间的类型,malloc
的返回值为void*, 在使用时必须强转,new不需要new
申请失败需要捕获异常,malloc
申请空间失败时,返回的是NULL
从底层来看 malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
new运算符的原理
1.内存分配
调用相应的 operator new(size_t) 函数,动态分配内存。如果 operator new(size_t) 不能成功获得内存,则调用 new_handler()函数用于处理new失败问题。如果没有设置 new_handler() 函数或者 new_handler() 未能分配足够内存,则抛出 std::bad_alloc 异常
2.构造函数
在分配到的动态内存块上 初始化相应类型的对象(构造函数)并返回其首地址。如果调用构造函数初始化对象时抛出异常,则自动调用 operator delete(void*, void*) 函数释放已经分配到的内存。
3.返回指针
new/delete的使用要点
new内置了sizeof
、类型转换和类型安全检查功能。
对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。
如果对象有多个构造函数,那么new的语句也可以有多种形式
#include <iostream>
using namespace std;
class Obj
{
public:
Obj(){} // 无参数的构造函数
Obj(int x):objx(x){} // 带一个参数的构造函数
int objx;
};
void Test(void)
{
Obj *a = new Obj; //无参数,构造初始化
cout << a->objx << endl; // 随机初始化
Obj *b = new Obj(200); // 含参,构造初始化
cout << b->objx << endl; // 初值为200
Obj *objects = new Obj[100]; // 创建100个动态对象
delete a;
delete b;
delete []objects; // 正确的用法
}
int main(){
Test();
return 0;
}
判断内存申请成功
如果在申请动态内存时找不到足够大的内存块,内存耗尽问题
判断指针是否为NULL
void Func(void){
A *a = new A;
if(a == NULL) { return; }
}
delete与delete[]区别
delete只会调用一次析构函数,而delete[] 会调用每一个成员的析构函数。
在More Effective C++中有更为详细的解释:
“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。”
delete与new配套,delete []与new []配套
-
调用析构函数
-
释放内存空间
int* a = new int(10);
int * arr = new int[10];
//相当域创建了地址, 必须需要通过指针进行接收
for (int i = 0; i < 10; i++){
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++){
cout << arr[i] << endl;
}
delete [] arr;