C++内存分布与动态内存管理

文章目录

  • :dizzy: C/C++内存分布
  • :dizzy:C语言中动态内存管理方式
    •   :sparkles:malloc
    •    :sparkles:calloc
    •   :sparkles:realloc
  • :dizzy:C++语言中动态内存管理方式
    •   :sparkles:new和delete操作内置类型
    •   :sparkles:new和delete操作自定义类型
  • :dizzy:operator new与operator delete函数
  • :dizzy:new和delete的实现原理
    •    :sparkles:内置类型
    •    :sparkles:自定义类型
  • :dizzy: 定位new表达式(placement-new)

💫 C/C++内存分布

  在认识内存管理之前我们需要先认识C/C++内存分布,首先声明一点这里的所讲的内存分布并不是物理内存的分布而是逻辑上的内存分布,逻辑内存与物理内存会以某种方式进行联系,这里不做过多说明,仅做初步了解。
  见下图:
在这里插入图片描述

  还是不够直观,怎么证明内存分布是如图形式

    1 #include<stdio.h>
    2 #include<stdlib.h>
    3 int g_val=100;
    4 int g_unval;                                                                                                 
    5 static int g_s_val=200;
    6 static int g_s_uval;
    7 int main(int argc,char*argv[],char*env[])
    8 {
    9   static int s_val=100;
   10   const char* p="hello world";
   11   char *heap1=(char*)malloc(10);
   12   char *heap2=(char*)malloc(10);
   13   char *heap3=(char*)malloc(10);
   14   char *heap4=(char*)malloc(10);
   15   printf("argv addr: %p\n",argv[0]);
   16   printf("env addr: %p\n",env[0]);
   17 
   18   printf("stack addr: %p\n",&heap1);
   19   printf("stack addr: %p\n",&heap2);
   20   printf("stack addr: %p\n",&heap3);
   21   printf("stack addr: %p\n",&heap4);
   22 
   23   printf("heap addr: %p\n",heap1);
   24   printf("heap addr: %p\n",heap2);
   25   printf("heap addr: %p\n",heap3);
   26   printf("heap addr: %p\n",heap3);
   27 
   28   printf("global unval addr: %p\n",&g_unval);
   29   printf("global unval addr: %p\n",&g_s_uval);
   30   printf("global val addr: %p\n",&g_s_val);
   31   printf("global val addr: %p\n",&g_val);
   32   printf("global val addr: %p\n",&s_val);
   33   printf("read onl addr: %p\n",p);
   34 
   35   printf("coad addr: %p\n",main);
   36   return 0;
   37 }             

在这里插入图片描述
简单介绍:

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量。

💫C语言中动态内存管理方式

  开辟内存相关函数(malloc,realloc,calloc)

  ✨malloc

void* malloc (size_t size);
开辟size字节大小的连续空间,开辟成功返回所开辟空间的地址,开辟失败返回NULL

   ✨calloc

void* calloc (size_t num, size_t size);
开辟num个size大小字节连续的空间,并为所开辟空间中每个字节赋初始值0,开辟成功返回所开辟空间的地址,开辟失败返回NULL

  ✨realloc

void* realloc (void* ptr, size_t size);
realloc可以用于开辟空间但最重要的是这个函数可以为所开辟空间扩容
realloc为ptr所指向空间扩容,将原始空间扩容为size字节,扩容成功返回所开辟空间地址,开辟失败返回NULL
注意:realloc扩容方式有两种
 1:原地扩容,扩容成功返回原来内存空间地址,空间地址并不发生变化
 2:异地扩容,扩容成功返回一个全新内存空间的地址,空间地址发生变化,之间空间的数据拷贝至新的内存空间。
注意:realloc也有缩容的作用,但是推荐不要使用,缩容存在数据丢失的风险。

注意:因为realloc开辟空间的方式并不确定,处理时要格外小心.
  错误用示范

void Test1()
{
	int* p1 = (int*)malloc(sizeof(int)*10);
	cout << p1 << endl;
	p1 = (int*)realloc(p1, sizeof(int) * 10000000000);
	cout << p1 << endl;
	cout << (void*)NULL << endl;
}

run:
在这里插入图片描述   以如上方式用realloc对p1空间进行扩容并发生异地扩容同时扩容失败的情况下,用指向旧空间的指针存放新空间的地址,会丢失旧空间的数据,所以正确使用方式如下:
  正确示范

void Test1()
{
	int* p1 = (int*)malloc(sizeof(int)*10);
	cout << p1 << endl;
	int* p2 = (int*)realloc(p1, sizeof(int) * 1000);
	if (p2==NULL)
	{
		cout << "开辟空间失败" << endl;
		exit(-1);
	}
	p1 = p2;
	cout << p1 << endl;
	
}

run:
在这里插入图片描述


💫C++语言中动态内存管理方式

  C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

  有痛点才能更加理解C++语言中动态内存管理方式的优势。

#include<iostream>
using namespace std;
class aim
{
public:
	aim(int temp)
		:_num(temp)
	{

	}
private:
	int _num;
};
int main()
{
	aim* p1 = (aim*)malloc(sizeof(aim));
	aim* p2 = new aim(100);
	free(p1);
	delete p2;
	return 0;
}

  思考用C的动态内存管理方式你要如何对p1进行管理?
  以C的动态内存管理方式开辟空间不会执行构造函数,对于类的构造函数我们无法显示调用构造函数,但是我们可以用定位new的方式解决初始化问题(见下文),以C的动态内存管理方式释放空间不会调用析构函数清理对象资源,但可以显示调用析构函数清理对象资源。
  但是用C++的动态内存管理方式我们就十分轻松的解决了初始化问题。
new会开辟空间并调用构造函数,delete会调用析构函数清理资源并释放空间。

  ✨new和delete操作内置类型

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr1 = new int;

	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);

	// 动态申请10个int类型的空间
	int* ptr3 = new int[3];
	delete ptr1;
	delete ptr2;
	delete[] ptr3;

	//以下方式在C++11下支持
	// 动态申请10个int类型的空间,并赋值
	int* ptr4 = new int[10] {1, 2, 3, 4};
	delete[] ptr4;
}

  ✨new和delete操作自定义类型

#include<iostream>
using namespace std;
class aim
{
public:
	aim(int temp)
		:_num(temp)
	{
		cout << this << endl;
	}
	~aim()
	{
		cout << this << endl;
	}
private:
	int _num;
};
int main()
{
	aim* p1 = (aim*)malloc(sizeof(aim));
	aim* p2 = new aim(100);
	cout << "p1->" << p1<<endl;
	cout << " p2 ->"<<p2 <<endl;
	free(p1);
	delete p2;
	return 0;
}

run:
在这里插入图片描述   我们可以清晰的看到用C的动态内存管理方式申请/释放空间不会调用构造函数/析构函数。
  申请多个内存空间

C++11支持用以下方式赋值
aim* p3 = new aim[5]{1,2,3,4,5};

  如果出现报错:无默认构造函数,可用此方式强制生成默认构造函数

aim() = default;

💫operator new与operator delete函数

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

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

  通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
  以汇编语言验证new的底层原理
在这里插入图片描述


💫new和delete的实现原理

   ✨内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

   ✨自定义类型

new的原理

  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

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

new T[N]的原理

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对 象空间的申请
  2. 在申请的空间上执行N次构造函数

delete[]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释 放空间

  一些疑问?
  为什么new T[N]方括号中要填入具体对象个数N而delete[]的方括号中不需要填入具体的对象个数?
  示例:

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int>* v = new vector<int>[10];
	cout << "V->"<<v << endl;
	delete[] v;
	return 0;
}

在这里插入图片描述
  我们可以清晰的看到在V的地址之前存储了需要释放空间个数,所以delete[]的方括号中不需要填入具体的对象个数,开辟的空间逻辑图如下:
在这里插入图片描述


💫 定位new表达式(placement-new)

  定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
  使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

  使用场景:定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

#include<iostream>
using namespace std;
class aim
{
public:
	aim() = default;
	aim(int temp)
		:_num(temp)
	{
		cout << this << endl;
	}
	~aim()
	{
		cout << this << endl;
	}
private:
	int _num;
};
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	aim* p1 = (aim*)malloc(sizeof(aim));
	new(p1)aim; // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~aim();//析构函数可以显示调用但是构造函数不可以显示调用
	free(p1);

	aim* p2 = (aim*)operator new(sizeof(aim));
	new(p2)aim(10);
	p2->~aim();
	operator delete(p2);
	return 0;
}

🌄感谢阅读🌄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/456035.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面试经典-16- 环形链表

题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#…

揭秘Kafka拦截器的神奇操作

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 揭秘Kafka拦截器的神奇操作 前言拦截器的基本概念Kafka 拦截器的定义和基本原理&#xff1a;拦截器是 Kafka 消息传递的不可或缺的组成部分的原因&#xff1a; 生产者拦截器配置和使用生产者拦截器的步…

C# 文件拖入控件中,显示文件路径

1.设置所需拖入的控件&#xff08;以Textbox为列&#xff09;属性为&#xff1a; this.textBox1.AllowDrop true; //设置AllowDrop 属性为 true&#xff0c;使之支持拖拽&#xff0c;否则拖拽显示禁用状态 2.设置该控件的两个事件&#xff0c;分别为&#xff1a; ①DragEnt…

Vcenter esxi web界面访问提示权限被拒绝

一、问题现象 原因 应该是在vCenter中添加主机的时候&#xff0c;将锁定模式设置成了严格。 二、解决过程 2.1 方式一 BMC登录主机&#xff0c;连接显示器和键盘。 输入账号密码&#xff0c;按F2进行设置&#xff0c;将会打开一个界面&#xff0c;第一个选项是设置密码&…

c++指针的定义和使用

1、定义一个指针 int a10; //定义指针的语法&#xff1a;数据类型 * 指针变量名&#xff1a;int * p&#xff1b; //让指针记录变量a的地址&#xff1a;p &a; int a 10;int* p; p &a; cout << "a的地址为&#xff1a;" << &a <<…

疯狂数钞票H5游戏

移动端微信h5 <template><div class"container" id"container"><div class"regBag"></div><div class"moneyBox"><transitionv-for"(item,index) in showImgList":key"index"…

微服务技术栈之rabbitMQ高级(二)

我们该如何确保MQ消息的可靠性&#xff1f; 如果真的发送失败&#xff0c;有没有其它的兜底方案&#xff1f; 这些问题&#xff0c;在这一次的学习中都会找到答案。 生产者的可靠性 首先&#xff0c;我们一起分析一下消息丢失的可能性有哪些。 消息从发送者发送消息&#…

leetcode一天一题-第1天

为了增加自己的代码实战能力&#xff0c;希望通过刷leetcode的题目&#xff0c;不断提高自己&#xff0c;增加对代码的理解&#xff0c;同时开拓自己的思维方面。 题目名称&#xff1a;两数之和 题目编号&#xff1a;1 题目介绍&#xff1a; 给定一个整数数组 nums 和一个整数…

Instant --java学习笔记

Instant 时间线上的某个时刻 / 时间戳过获取lnstant的对象可以拿到此刻的时间&#xff0c;该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数不够1秒的纳秒数 Instant的常见方法&#xff1a; Instant可以用来记录代码的执行时间&#xff0c;或用于记录用户操作某…

利用Nginx正向代理实现局域网电脑访问外网

引言 在网络环境中&#xff0c;有时候我们需要让局域网内的电脑访问外网&#xff0c;但是由于网络策略或其他原因&#xff0c;直接访问外网是不可行的。这时候&#xff0c;可以借助 Nginx 来搭建一个正向代理服务器&#xff0c;实现局域网内电脑通过 Nginx 转发访问外网的需求。…

macbook使用Parallels Desktop虚拟机中使用外接拓展屏幕

macbook使用安装了windows虚拟机后&#xff0c;想让windows使用macbook外接的拓展屏&#xff0c;其实很简单&#xff0c;只需要在parallels desktop中点击全屏开启&#xff1a; 就可以在windows全屏模式下使用拓展屏幕了

Docker 镜像源配置

目录 一、 Docker 镜像源1.1 加速域名1.2 阿里云镜像源&#xff08;推荐&#xff09; 二、Docker 镜像源配置2.1 修改配置文件2.1.1 Docker Desktop 配置2.1.2 命令行配置 2.2 重启 Docker 服务2.2.1 Docker Desktop 重启2.2.2 命令行重启 2.3 检查是否配置成功 参考资料 一、 …

嘿!终于等到了!应用开发云资源套餐如约而至!

MemFire Cloud平台更新啦&#xff01;&#xff01;此次更新我们推出了万众期待的计费套餐&#xff0c;下面给大家带来详细的介绍~ 计费模式为“基础套餐按量付费”&#xff0c;您可选择购买带有一定配额的基础套餐&#xff0c;超出配额部分可以通过开启“超限按量”功能来转为…

清华大学:《AIGC发展研究资料2.0》

清华大学发布了《AIGC发展研究资料2.0》&#xff0c;该报告旨在聚焦AIGC产业发展的现状、趋势&#xff0c;从技术篇、产业篇、评测篇、职业篇、风险篇等多种角度分析产业发展。 报告还强调了该技术的应用潜力将在教育、医疗、工业制造、交通运输、法律服务等领域发挥&#xff0…

学会这几步,让酷开系统的使用体验更加出色!

在当今数字化快速发展的时代&#xff0c;用户体验&#xff08;User Experience, UX&#xff09;已成为产品和服务成功的关键因素之一。随着市场竞争的加剧&#xff0c;仅仅提供功能性强大的产品已不足以满足用户的需求&#xff0c;如何提升整体体验、确保用户的满意度和忠诚度&…

AutoMQ 社区双周精选第八期(2024.02.26~2024.03.08)

本期概要 本周新增贡献者&#xff1a; tisonkun: 优化了 E2E 测试在 Fork 仓库的定期执行问题。 funky-eyes: 修复了 s3url 未透传 pathStyle 的问题&#xff0c;并支持 HTTP S3 接入点。 版本发布重大更新&#xff1a; AutoMQ 1.0.0 GA : 经过长时间的自动化测试验证&…

OSCP-Challenge 1 - Medtech

文章目录 121靶机122靶机14靶机11靶机83靶机82靶机12靶机13靶机10靶机120靶机121靶机 进入首页后有个登录功能,点击跳转到login.aspx 在用户名处存在sql注入,sql类型是mssql。 直接用xp_cmdshell执行命令。 后面想着用powershell来反弹shell或者下载文件,发现均失败,然后…

从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载

本文为从零开始写 Docker 系列第六篇&#xff0c;实现类似 docker -v 的功能&#xff0c;通过挂载数据卷将容器中部分数据持久化到宿主机。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; …

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人群密度检测系统(深度学习模型+UI界面+训练数据集)

摘要&#xff1a;开发人群密度检测系统对于公共安全等领域具有关键作用。本篇博客详细介绍了如何运用深度学习构建一个人群密度检测系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并对比了YOLOv7、YOLOv6、YOLOv5&#xff0c;展示了不同模型…

4 配置静态IP

当我们安装好Linux后&#xff0c;需要进行网络配置&#xff0c;保障windows和linux网络相通&#xff0c;以及通过Linux可以访问外网。 1、设置VM网络&#xff1a; 1.1 选择编辑---虚拟网络编辑器 1.2 选择VMnet8设置&#xff0c;可以使用默认网段52也可以通过点击更改设置对其…
最新文章