首页 > 系统相关 >Cpp内存管理(7)

Cpp内存管理(7)

时间:2024-09-26 13:23:11浏览次数:3  
标签:malloc 管理 int free 内存 Cpp 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类型的空间并初始化为10
	int* p5 = new int(10); //申请 + 赋值
	delete p5; //销毁
	
	// 动态申请一个int类型的空间并初始化为10
	int* p6 = (int*)malloc(sizeof(int)); //申请
	*p6 = 10; //赋值
	free(p6); //销毁

	// 动态申请10个int类型的空间并初始化为0到9
	int* p7 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //申请 + 赋值
	delete[] p7; //销毁

	// 动态申请10个int类型的空间并初始化为0到9
	int* 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 bytes
	void* p;
	while ((p = malloc(size)) == 0) // 注意这里,就是malloc
		if (_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_FINALLY
 
	return;
}

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

 可以看出 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在释放空间前会调用析构函数完成
    空间中资源的清理释放 (这是原理,希望你对此有个深刻的印象)

总结

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

标签:malloc,管理,int,free,内存,Cpp,new,delete
From: https://blog.csdn.net/2301_80392199/article/details/142549200

相关文章

  • HDFS 节点动态管理
    一、节点上线1.新机器安装环境准备参考集群安装文档环境准备2.namenode节点配置[root@hdp01hadoop]#catworkershdp01.dialev.comhdp02.dialev.comhdp03.dialev.comhdp04.dialev.com[root@hdp01hadoop]#pwd/usr/local/hadoop/etc/hadoop[root@hdp01hadoop]#cd/us......
  • 个微自动化,让多微管理更智能、更高效!
    当你有好几个微信号需要管理时,自动化设置简直是救星!今天,要给大家推荐一个很哇塞的多微管理工具,让你的微信号管理变得简单又高效!一起来看看它都有哪些自动化设置吧!1、自动添加好友这个系统支持批量导入客户号码,多账号同时进行自动加好友。再也不用一个个手动添加好友啦,省下的......
  • Linux服务器运维管理面板1Panel快速安装及安全配置
    1Panel是一个现代化、开源的Linux服务器运维管理面板,旨在帮助运维人员简化服务器管理任务。它提供了直观的界面和强大的功能,使用户可以通过图形化操作界面对服务器进行管理,减少了对命令行的依赖。1Panel支持多种操作系统,适用于Linux服务器,提供了如网站管理、数据库管......
  • 基于springboot的企业人事管理系统的设计与实现 (含源码+sql+视频导入教程+论文)
    ......
  • 【程序大侠传】应用内存缓步攀升,告警如影随形
    前序在武侠编码的江湖中,内存泄漏犹如隐秘杀手,潜伏于应用程序的各个角落,悄无声息地吞噬着系统资源。若不及时发现和解决,必将导致内存枯竭,应用崩溃。背景:内存泄漏的由来内存泄漏,乃程序运行过程中,已不再使用的内存块未被及时回收,导致内存使用量不断增加的现象。此问题多发于对象生命......
  • 基于springboot和vue的教务学生选课管理系统的设计与实现 (含源码+sql+视频导入教程)
    ......
  • Python日志管理之Loguru
    1.安装pipinstallloguru2.快速使用fromloguruimportloggerlogger.add("my_log.log",rotation="10MB")#自动分割日志文件logger.info("这是一个信息级别的日志")3.日志器配置方式1.导入即用fromloguruimportlogger,有且只有1个日志器对象,简化配置复杂性2.日志器配......
  • 计算机专业毕设选题推荐-基于python的企业工作考勤管理系统 企业员工考勤系统
    精彩专栏推荐订阅:在下方主页......
  • jsp电影推送及电影数据管理系统3f6db
    jsp电影推送及电影数据管理系统本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能用户,电影类型,电影信息开题报告内容一、项目背景与意义随着数字化时代的到来,电影产业正经历着前所未有的变革。观......
  • jsp地铁运营管理系统设计与实现5372j
    jsp地铁运营管理系统设计与实现本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能公告类型,公告信息,部门,员工,工作类型,分配工作,设备信息,设备安排,用户,问题反馈,线路信息,购票订单技术要求:  ......