当前位置: 首页 > news >正文

Cpp内存管理(7)

文章目录

  • 前言
  • 一、C/C++内存区域划分
  • 二、C/C++动态内存管理
    • C语言动态内存管理
    • C++动态内存管理
      • 对于内置类型
      • 对于自定义类型
  • 三、new和delete的底层实现
  • 四、new和delete的实现原理
  • 五、定位new
  • 六、malloc/free和new/delete的区别
  • 总结


前言

  软件开发过程中,内存管理的重要性不言而喻
  因此我们有必要了解一下C++中关于内存管理的一些特性

C++对内存的自由度使其获得了更高的性能,以及更高的难度。
内存泄漏往往是每个C++学习者绕不开的错误, 而内存管理的水平高低也能看出一个编程者的能力


一、C/C++内存区域划分

我们先来看一下以下代码:

int globalVar = 1;           // 全局变量
static int staticGlobalVar = 1; // 静态全局变量void Test() {static int staticVar = 1; // 静态局部变量int localVar = 1;         // 局部变量int num1[10] = {1, 2, 3, 4}; // 局部数组char char2[] = "abcd";    // 字符数组const char* pChar3 = "abcd"; // 字符指针常量int* ptr1 = (int*)malloc(sizeof(int) * 4);  // 动态分配内存int* ptr2 = (int*)calloc(4, sizeof(int));  // 动态分配并初始化int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); // 重新分配内存free(ptr1); // 释放内存free(ptr3);
}

你能说出globalVal、staticGlobalVar、staticVar、localVar、num1、*num1存在哪里吗?

在C++中,程序的内存区域从低地址到高地址划分如下:

  1. 代码段:存储可执行程序的代码和只读常量
  2. 数据段:存储已初始化的全局变量和静态变量
  3. 堆:用于程序运行时动态内存分配,从低地址向高地址增长
  4. 栈:又叫堆栈,存储非静态局部变量/函数参数和返回值等,从高地址向低地址增长

图形语言如下:
在这里插入图片描述

  我们倒回去看,我挑几个比较有意思的来细讲,你也可以重点关注一下,staticVar、char2、*char2、pChar3、*pChar3、*ptr2分别存储在哪里

变量名存储段
staticVar静态局部变量,存在数据段
char2字符指针变量,存在栈
*char2数组元素,存在栈,不是存在代码段,因为不是只读!!!
pChar3局部指针变量,存在栈
*pChar3只读变量,存在代码段
*ptr2动态分配的内存,存在堆

哦,对了,关于上面所说的“栈是向下增长的,而堆是向上增长的”,
简单来说就是在栈区开辟空间,先开辟的空间地址较高,而在堆区开辟空间,先开辟的空间地址较低
你可以通过以下代码来验证一下:

// 实在验证不出来就算了,这个跟编译器等环境关系很大
#include <iostream>
using namespace std;
int main()
{// 栈区开辟空间,先开辟的空间地址高int a = 10;int b = 20;cout << &a << endl;cout << &b << endl;// 堆区开辟空间,先开辟的空间地址低// 具体实现中,关于堆我们可能会验证失败// 你可以试着想一下这是为什么?int* c = (int*)malloc(sizeof(int)* 10);cout << c << endl;// free(c);加了这行,发现两个输出相同// 说明在堆区,后开辟的空间也有可能位于前面某一被释放的空间位置int* d = (int*)malloc(sizeof(int)* 10);cout << d << endl;return 0;
}

二、C/C++动态内存管理

C语言动态内存管理

  我们来回顾一下几种用于动态分配内存的函数:malloc、calloc、realloc 和 free,这些函数用于在程序运行时动态地分配和释放内存

malloc:用于分配指定大小的内存块,内存中的内容未初始化
calloc:类似于 malloc,但会将内存初始化为零。它的参数为元素的数量和每个元素的大小
realloc:用于调整之前分配的内存块的大小,如果新大小大于原大小,可能会移动内存块的位置

来个示例代码:

int* ptr1 = (int*)malloc(sizeof(int) * 4);  // 分配4个int类型大小的内存块
int* ptr2 = (int*)calloc(4, sizeof(int));   // 分配并初始化4个int类型大小的内存块
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); // 重新分配内存free(ptr1);
free(pter2);
free(ptr3);

C++动态内存管理

  C++继承了C语言的内存管理方式,并在此基础上引入了newdelete操作符,提供更方便的动态内存管理机制,这并不奇怪,因为C++本来就是祖师爷觉得C麻烦,在其基础上发展而来的

new 和 delete 适用于对象的动态内存分配,并且会自动调用构造函数和析构函数,这很重要

对于内置类型

  对于内置类型,其实 new 和 delete 在底层上多大的差别,只是使用的规则要有所区分

以下相对应内容等价

	// 动态申请单个int类型的空间int* p1 = new int; //申请delete p1; //销毁// 动态申请单个int类型的空间int* p2 = (int*)malloc(sizeof(int)); //申请free(p2); //销毁// 动态申请10个int类型的空间int* p3 = new int[10]; //申请delete[] p3; //销毁// 动态申请10个int类型的空间int* p4 = (int*)malloc(sizeof(int)* 10); //申请free(p4); //销毁// 动态申请单个int类型的空间并初始化为10int* p5 = new int(10); //申请 + 赋值delete p5; //销毁// 动态申请一个int类型的空间并初始化为10int* p6 = (int*)malloc(sizeof(int)); //申请*p6 = 10; //赋值free(p6); //销毁// 动态申请10个int类型的空间并初始化为0到9int* p7 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //申请 + 赋值delete[] p7; //销毁// 动态申请10个int类型的空间并初始化为0到9int* p8 = (int*)malloc(sizeof(int)* 10); //申请for (int i = 0; i < 10; i++) //赋值{p8[i] = i;}free(p8); //销毁

申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[ ]和delete[ ]

对于自定义类型

 new会调用构造函数,delete会调用析构函数,而malloc和free不会,原理下文再来解释

三、new和delete的底层实现

 new和delete并不是函数,而是用户进行动态内存申请和释放的操作符

但是其底层还是需要调用函数
且虽然函数名中带operator,但并不是重载函数,具有很强的误导性!!!

  new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间

我们来看一下operator new 和 operator free 的底层实现:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p = malloc(size)) == 0) // 注意这里,就是mallocif (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}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); // 注意这里,就是free__FINALLY_munlock(_HEAP_LOCK);  /* release other threads */__END_TRY_FINALLYreturn;
}

看不懂?看不懂就对了,我也看不懂,但是你注意一下我两个特意的注释点

 可以看出 operator new 实际上也是通过 malloc 来申请空间的,如果 malloc 申请空间成功就直接返回,如果失败则执行用户提供的应对措施,如果用户提供该措施则继续申请空间,否则抛出异常

其实,这也叫封装,就像引用的底层也是用指针的方式实现的

在这里插入图片描述

四、new和delete的实现原理

内置类型无非就是包一下,加个抛出异常,而对于自定义类,就复杂了

一、new的原理
  调用operator new函数申请空间,在申请的空间上执行构造函数,完成对象的构造

二、delete的原理
  在空间上执行析构函数,完成对象中资源的清理工作,调用operator delete函数释放对象的空间

三、new T[N]的原理
  调用operator new[]函数,而operator new[]函数实际上又会调用operator new函数完成N个T类型对象的空间申请,在申请的空间上执行N次构造函数

四、delete[]的原理
  在空间上执行N次析构函数,完成N个对象的资源清理调用operator delete[]函数,而operator delete[]函数又会调用operator delete函数来释放空间

五、定位new

  定位new表达式用于在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

  其实这个内容,你可以暂时做个了解,因为这个一般搭配内存池使用,这又牵扯到一个概念叫做池化技术,而内存池分配出的内存没有初始化,所以如果是自定义类型的对象,就需要使用定位new表达式进行显示调用构造函数进行初始化

#include <iostream>
using namespace std;class A
{
public:A(int a = 0) // 构造函数 :_a(a){}~A() // 析构函数{}
private:int _a;
};int main()
{// new(place_address)type 形式A* p1 = (A*)malloc(sizeof(A));new(p1)A;// new(place_address)type(initializer-list) 形式A* p2 = (A*)malloc(sizeof(A));new(p2)A(2021);// 析构函数也可以显示调用// 这就是为什么只有定位new,没有定位delete的缘故p1->~A();p2->~A();return 0;
}

六、malloc/free和new/delete的区别

首先,它们的共同点都是都是从 堆 上申请空间,并且需要用户手动释放

不同点,那就多了:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
    如果是多个对象,[ ]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
    要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
    在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成
    空间中资源的清理释放 (这是原理,希望你对此有个深刻的印象)

总结

  哈,本节内容还是蛮轻松惬意的,至少跟类和对象比起来是这样
  那么现在,我们来接着往下看模板
  相信我,这会更有意思!!!

相关文章:

  • 海信智能电视的使用心得
  • Elasticsearch 启动后在浏览器输入http://localhost:9200 访问失败
  • 企微SOP:构建标准化运营流程,驱动企业高效增长
  • 【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题
  • PHP 函数
  • BeautifulSoup4在爬虫中的使用
  • Oracle DB运维常用的视图及数据字典
  • linux命令之docker用法
  • Linux·进程概念(上)
  • OJ在线评测系统 后端基础部分开发 完善CRUD相关接口
  • 算法分享——《滑动窗口》
  • 海尔嵌入式硬件校招面试题及参考答案
  • 如何在Excel中快速找出前 N 名,后 N 名
  • Spring面试题——第二篇
  • 《论软件架构建模技术与应用》写作框架,软考高级系统架构设计师
  • [译]CSS 居中(Center)方法大合集
  • 2017 前端面试准备 - 收藏集 - 掘金
  • ECS应用管理最佳实践
  • Js基础知识(四) - js运行原理与机制
  • Map集合、散列表、红黑树介绍
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 初识MongoDB分片
  • 电商搜索引擎的架构设计和性能优化
  • 分享几个不错的工具
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 配置 PM2 实现代码自动发布
  • 使用Swoole加速Laravel(正式环境中)
  • 正则学习笔记
  • 智能合约开发环境搭建及Hello World合约
  • k8s使用glusterfs实现动态持久化存储
  • MPAndroidChart 教程:Y轴 YAxis
  • python最赚钱的4个方向,你最心动的是哪个?
  • 阿里云ACE认证之理解CDN技术
  • 容器镜像
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #Z2294. 打印树的直径
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • ${ }的特别功能
  • (1)bark-ml
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (第二周)效能测试
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (二)Eureka服务搭建,服务注册,服务发现
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (四)js前端开发中设计模式之工厂方法模式
  • (五)activiti-modeler 编辑器初步优化
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • .bat文件调用java类的main方法