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;

参考资料

https://juejin.cn/post/7306814408595603506

C++ 动态内存分配(new,delete)

C++ 内存管理(建议收藏)

posted @ 2024-02-21 21:07  贝壳里的星海  阅读(17)  评论(0编辑  收藏  举报