C/C++【内存管理】

简介: C++中的内存管理机制和C语言是一样的,但在具体内存管理函数上,C语言的malloc已经无法满足C++面向对象销毁的需求,于是祖师爷在C++中新增了一系列内存管理函数,即 new 和 delete著名段子:如果你还没没有对象,那就尝试 new 一个吧

✨个人主页: Yohifo

🎉所属专栏: C++修行之路

🎊每篇一句: 图片来源


Love is a choice. It is a conscious commitment. It is something you choose to make work every day with a person who has chosen the same thing.


爱是一种选择。这是一种有意识的承诺。这是你选择每天与一个选择同样事情的人一起工作的东西。



4f84ccd376983ab5c0e3c018d8a2b0a.png

📘前言


C++中的内存管理机制和C语言是一样的,但在具体内存管理函数上,C语言的malloc已经无法满足C++面向对象销毁的需求,于是祖师爷在C++中新增了一系列内存管理函数,即 new 和 delete

著名段子:如果你还没没有对象,那就尝试 new 一个吧

ab79af18ee04257198e66ec8893db21.png



📘正文


将内存分成不同区域是为了实现更好的管理,比如在我们现实生活中,一幢房子会被分为客厅、厨房、卧室、卫生间等区域,目的是为了使我们生活更加方便、空间利用更加合理,计算机也是如此,更何况是空间非常珍贵的内存,因此在我们的程序中存在不同内存分区

def581dd9d00072f139bbb62ae5ad89.png



📖内存分布


在程序中存在五大分区,各个分区各司其职,比如我们耳熟能详的栈区、堆区、静态区


🖋️五大分区


栈区:


栈又称做堆栈,用于存储非静态局部变量、函数参数、返回值等,栈的空间是向下增长的

内存映射段:


内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信

堆区:


堆用于程序运行时动态内存分配,堆是可以上增长的,我们的动态内存就是在堆上申请的

数据段:


数据段中负责存储全局数据和静态数据

代码段:


代码段中存储可执行的代码/只读常量

注意: 内存中还存在内核空间,但我们普通用户代码无法读写


🖋️图解


通过具体代码展示具体分布如下


5aece4ec236c92a77fe466d254c0ab2.png


📖重温


先简单回顾下C语言中的动态内存管理


🖋️malloc/calloc/realloc


C语言提供了三种动态内存管理函数


malloc:申请指定大小的空间


int* pi = (int*)malloc(sizeof(int) * 1);  //申请一个整型
double* pd = (double*)malloc(sizeof(double) * 2); //申请两个浮点型
char* pc = (char*)malloc(sizeof(char) * 3); //申请三个字符型


注意: malloc申请的空间都是未初始化的,即被编译器置为随机值


calloc:将申请的空间初始化为 0


int* pi = (int*)calloc(1, sizeof(int)); //申请一个整型
double* pd = (double*)calloc(2, sizeof(double));  //申请两个个浮点型
char* pc = (char*)calloc(3, sizeof(char));  //申请三个字符型


注意: calloc参数列表与malloc不同,同时calloc申请的空间会被初始化为 0


realloc:对已申请的空间进行扩容


int* tmp = (int*)realloc(pi, sizeof(int) * 10); //将 pi 扩容为十个整型
pi = tmp; //常规使用方法


注意: 我们要对所有的申请函数进行空指针检查,预防野指针问题


堆区的空间由我们管理,编译器很信任我们,因此我们要做到有借有还,再借不难


凡是动态开辟的空间,用完后都需要释放


🖋️free

C语言提供的空间释放函数是free

free(tmp);  //此时tmp指向pi扩容后的空间,释放tmp就行了
tmp = pi = NULL;  //两者都需要置空
free(pd);
pd = NULL;
free(pc); //只要是动态开辟的,都需要通过 free 释放
pc = NULL;


注意: 只有动态开辟的空间才能使用 free,同时一块空间不能释放两次。我们在 free 后通常会把指针置空


关于C语言动态内存管理更多细节可以看看这篇文章:《C语言动态管理》

这里就不再阐述


C语言 中管理函数只能对内置类型使用,而 C++ 中存在很多自定义类型,常规 malloc 等函数无能为力


📖初识


出现了新的关键字:new 和 delete,它们也有很多形式和使用细节


🖋️new


使用:


int* pi = new int;  //申请一个整型
double* pd = new double(3.14);  //申请一个浮点型并初始化为 3.14
char* pc = new char[5] {'H', 'e', 'l', 'l', 'o'}; //申请五个字符型,并分别初始化为 Helloc


注意:


new 与 malloc等不同,不需要进行空指针检查,也不需要进行类型转换

new 的使用极其简单

特点:


new可以用于自定义类型

动态开辟时,会调用自定义类型的构造函数

//假设存在日期类
Date* ptr = new Date[5];  //申请五个日期类,这些类都在堆上


下面来看看C++中的内存释放函数


🖋️delete


形式:


delete 指针

delete[] 指针

使用:


C语言中的 free 可以用于释放所有动态申请函数,而 C++ 不行,申请与释放需要配套使用

int* pi = new int;
delete pi;  //直接释放
double* pd = new double[5];
delete[] pd;  //释放五次
Date* ptr = new Date[5];
delete[] ptr; //释放五次,即调用五次日期类的析构函数


注意:


需要配套使用,new int 搭配 delete,而 new int[] 需要搭配 delete[]

特点:


delete可以用于自定义类型

调用销毁时,会先调用自定义类型的析构函数

🖋️特点


C语言和C++动态内存管理函数的最大区别是: 是否会调用自定义类型的构造函数和析构函数


C语言明显不会,毕竟那时候还没有这些概念,而 C++ 作为面向对象的语言,调用构造与析构函数是必然的


C语言中的申请函数不能通过C++的释放函数进行释放,同理C++的申请空间也不能通过C语言的释放函数进行释放,比如下面这些情况是不合理的,可能引发问题


int* cPi = (int*)malloc(sizeof(int));
delete cPi; //不合理的操作
int* cppPi = new int;
free(cppPi);  //这样也是不合理的
Date* ptr = new Date;
free(ptr);  //此时会报错,因为 free 并不会调用析构函数
1


切记,申请与释放要配套使用


📖探究


为何C++中的动态内存管理函数能做到调用构造和析构函数呢?


这是因为我们也是调用的其他函数,正是得益于C++中的封装

🖋️封装实现


new 和 delete 是用户进行动态内存申请和释放的 操作符,它们在实现时会去调用真正的全局函数 operator new 与 operator delete,具体调用情况如下所示:


new 与 new []


new 调用 operator new

new [] 调用 operator new[]

delete 与 delete []


delete 调用 operator delete

delete [] 调用 operator delete[]

注意: operator new[] 最终是调用 operator new,operator delete[] 最终也是调用 operator delete

所以严格来说,operator new 和 operator delete 才是我们探讨的主角


🖋️代码展示


先来看看 operator new 的代码实现


/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECLoperatornew(size_tsize)_THROW1(_STDbad_alloc)
{
  // try to allocate size bytes
  void* p;
  while ((p = malloc(size)) == 0)
  if (_callnewh(size) == 0)
  {
    // report no memory
    //如果申请内存失败了,这里会抛出bad_alloc类型异常
    staticconststd::bad_allocnomem;
    _RAISE(nomem);
  }
  return(p);
}

//代码源自:比特教育科技 https://www.bitejiuyeke.com/index



可以看到,其实 operator new 就是通过对 malloc 的封装实现的,不过进行了改进,当对象为自定义类型时,会去调用它的构造函数,并且当开辟失败时,会抛出异常


再来看看 operator delete 的代码实现


/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
  _CrtMemBlockHeader* pHead;
  RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
  if (pUserData == NULL)
  return;
  _mlock(_HEAP_LOCK); /* block other threads */
  __TRY
  /* get a pointer to memory block header */
  pHead = pHdr(pUserData);
  /* verify block type */
  _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
  _free_dbg(pUserData, pHead->nBlockUse);
  __FINALLY
  _munlock(_HEAP_LOCK); /* release other threads */
  __END_TRY_FINALLY
  return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
//代码源自:比特教育科技 https://www.bitejiuyeke.com/index



operator delete 的代码中也有 free 的影子,当释放对象为自定义类型时,会调用它的析构函数


📖new/delete 实现步骤


下面再来看看两者具体的实现步骤


🖋️内置类型


对于内置类型来说,使用 malloc/free 和 new/delete 没什么区别


🖋️自定义类型


对于自定义类型,new/delete 的实现步骤如下:


new


调用 operator new 申请空间

在申请的空间上调用构造函数

delete


在空间上调用析构函数

再调用 operator delete 释放空间

new []


调用 operator new[] 函数,根据数值N,调用N次 operator new 函数申请空间

在申请的空间上调用N次构造函数

delete []


在申请的空间上调用N次析构函数

调用 operator delete[] 函数,然后由函数再调用 operator delete 释放空间

📖定位new


定位new 是 new 的新用法


目的:


对已开辟而未初始化的空间进行初始化

形式:


new(指针)构造函数

//定位new
Stack* ptr = (Stack*)malloc(sizeof(Stack)); //malloc 不会调用构造函数,此时未初始化
new(ptr)Stack();  //通过定位new初始化对象


🖋️应用场景


定位new 可以用在内存池这个项目中


向堆中申请一块定额空间,此时空间未初始化

此时就需要通过 定位new 来进行初始化

7e3ac9d2ca983a38b517bff4fca0807.png

📖注意事项


开辟与释放需要配对使用


malloc/calloc/realloc 搭配 free


new 搭配 delete


new [] 搭配 delete []


📘总结


以上就是关于 C++ 内存管理的全部内容了,记住一点就够了:认识 new ,配对使用。


如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!


如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


目录
相关文章
|
2月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
1月前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
53 3
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
108 4
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
160 21
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
53 0
【C++打怪之路Lv6】-- 内存管理
|
2月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
92 1
|
2月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
392 1
|
2月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
104 3