【C++ 初阶路】--- C++内存管理

目录

  • 一、C/C++内存分布
  • 二、C++内存管理方式
    • 2.1 new/delete操作内置类型
    • 2.2 new和delete操作自定义类型
  • 三、operator new与operator delete函数
  • 四、new和delete的实现原理
    • 4.1 内置类型
    • 4.2 自定义类型

一、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);
}
  1. 选择题:

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?

  • 全局变量,静态区

staticGlobalVar在哪里?

  • 静态全局变量,静态区

staticVar在哪里?

  • 静态局部变量,生命周期延长,静态区

localVar在哪里?

  • 局部变量,出了函数作用域就销毁,栈区

num1在哪里?

  • 在栈上开辟的数组num1,大小40字节,出栈销毁,数组名num1为指向第一个元素的指针,存放在 栈区

char2在哪里?

  • "abcd"原是在常量区,后拷贝到栈区形成数组,char2指向栈上数组的第一个字符,存放在 栈区

*char2在哪里?

  • 由上,解引用*char2得到拷贝到栈上的数组的第一个字符a栈区

pChar3在哪里?

  • 字符串"abcd"在常量区,pChar3指向这个字符串(地址),但pChar3本身为指针,存放在 栈区

*pChar3在哪里?

  • 由上*pChar3,解引用后指向常量字符串,在 常量区

ptr1在哪里?

  • 同理,malloc()在堆区上开辟了一段空间,ptr1指针指向这段动态开辟的堆区空间,指针本身还在 栈区

*ptr1在哪里?

  • 由上,malloc()开辟空间在堆区,*ptr1解引用后拿到堆区上的数据,所以在 堆区
  1. 填空题:
    sizeof(num1) = 40; // sizeof(int)* 10 — sizeof(数组名),此时为整个数组大小
    sizeof(char2) = 5; // 还有一个'\0'
    strlen(char2) = 4; // 到'\0'结束,此时char2为字符数组的第一个元素的地址
    sizeof(pChar3) = 4 or 8; // 指针大小固定为 4 or 8 区别在于机器位数
    strlen(pChar3) = 4;
    sizeof(ptr1) = 4 or 8;
    如还需进一步了解,还可参考 详解sizeof()和strlen()的细节及用法 一文。sizeof是一个运算符,在编译时根据类型大小定义,自定义类型根据内存对齐规则计算大小(编译时就是一个具体的值!);而strlen是一个函数,运行时计算字符长度,事实上在编译时就没有此函数了,被转换为了一个个指令(即call此函数实现地址,执行内部指令`)!

在这里插入图片描述

【说明】

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

C语言中动态内存管理方式:malloc/calloc/realloc/free

【面试题】: malloc/calloc/realloc的区别? 参考 【c语言】详解动态内存管理 一文。

二、C++内存管理方式

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

2.1 new/delete操作内置类型

  1. 用法上,变简洁了
int* p0 = (int*)malloc(sizeof(int));
int* p1 = new int;
int* p2 = new int[10]; // new 10 个int 对象

delete p1;
delete[] p2;
  1. 可以控制初始化
int* p3 = new int(10); //动态申请一个int类型的空间
int* p4 = new int[10] {1, 2, 3, 4, 5};  //动态申请十个int类型的空间并初始化为{...}, 其余为0

注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用new[]delete[] 注意:匹配起来使用。

2.2 new和delete操作自定义类型

  1. new/delete对于自定义类型除了开空间还会调用构造函数和析构函数,内置类型是几乎是一样的
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		std::cout << "A():" << this << std::endl;
	}
	~A()
	{
		std::cout << "~A():" << this << std::endl;
	}
private:
	int _a;
};
int main()
{
	// new/delete 和 malloc/free最大区别是 
	// new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;
	return 0;
}

在这里插入图片描述

调用new动态开辟内存,编译器会自动帮我们计算要开辟的空间,并调用operator new全局函数(其是对malloc的封装,失败抛异常也是在这一层,为了实现new),然后再调用自定义类型的构造函数。从汇编角度,如下:

new [n]是会调用operator new[]函数(其是对operator new的封装) 和 n 次构造函数。

在这里插入图片描述


delete释放空间也相似,只不过先调用析构函数,再释放空间。 至于为什么,参考如下情况:

class MyStack
{
public:
	MyStack()
		: _a((int*)malloc(sizeof(int) * 4))
		,_capacity(4)
		, _top(0)
	{}

	~MyStack()
	{
		free(_a);
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _capacity;
	int _top;
};

int main()
{
	MyStack* st = new MyStack;
	delete st;
	return 0;
}

若先调用operator delete_a指针变量所在的地址空间将被释放,无法找到malloc开辟的堆上空间!

在这里插入图片描述


再来观察如下现象,new A开辟的是4字节空间,但是new A[10]开辟的却是44字节空间,这是为什么呢?

在这里插入图片描述

new A调用一次operator new和一次构造函数;同理new A[10]调用十次operator new和十次构造函数,因为[]中传有开辟对象个数。那么delete调用一次析构和一次operator delete,但是delete[]可就不一样了,因为[]中没有传析构次数,所以编译器就不知道。那么为了让编译器知道次数,就在开辟的空间顶上多开辟4个字节来存放对象个数(X86环境,实测X64环境下多开辟8字节),只有这样delete[]才知道调用多少次析构函数。

在这里插入图片描述

当然也有很多情况不会在顶上多开辟空间:1. new内置类型,不需要析构;2. 没有显示写析构函数的自定义类型。(基于编译器的优化)


newdelete不匹配问题:

一个非常典型的问题(基于编译器的优化)就是:new多个自定类型时(A* p = new A[10]),且直接使用delete A,如果A类显示实现析构函数就会报错,如果不写析构函数就不会报错! 这与上面那个问题密切相关,即是否多开辟空间存对象个数。

如果显示实现了析构函数,p3并没有指向动态开辟内存的起始位置,且delete又不知道要向前偏移,所以直接释放了动态开辟的内存的中间位置,导致报错! 而不实现析构函数,就不会多开辟空间,也就避免了这样的问题。当然两者情况都可能会导致内存泄漏的问题!

在这里插入图片描述

所以newdelete一定要匹配使用,因为导致的结果可能是不确定的!


  1. new失败了以后抛异常,不需要手动检查,捕获异常方式:
try
{
	func(); // 其中调用new
}
catch(const std::exception& e)
{
	std::cout << e.what() << std::endl;
}

运用如上这些定理我们自己实现单链表也变得方便的多了!首先我们可以先创建一个类来描述单链表,然后单独实现创建链表的函数。

可以先创建一个哨兵位(MyList head(-1);栈上开辟,此节点为了方便后续链表节点的链接,且在创建单链表函数结束时自动销毁);然后通过cin输入链表节点值(val),并在堆上开辟链表节点(new MyList(val);,此时还会调用MyList类的构造函数);最后再链接各节点,并返回哨兵位后一个节点(head._next),即链表初始节点(哨兵位节点,栈上空间,出作用域自动销毁)。

//C++中List单链表的创建
struct MyList
{
	MyList(int val = 0)
		:_next(nullptr)
		,_val(val)
	{}

	MyList* _next;
	int _val;
};

MyList* CreatList(int n)
{
	MyList head(-1);//哨兵位  ---  出栈销毁

	MyList* tail = &head;
	int val;
	std::cout << "请以此输入" << n << "个节点的值:> " << std::endl;
	for (size_t i = 0; i < n; i++)
	{
		std::cin >> val;
		tail->_next = new MyList(val);  // 堆上开辟,链表实体; 且自动调用构造函数
		tail = tail->_next;
	}
	//返回哨兵位后面一个节点
	return head._next;
}
int main()
{
	MyListNode* head = CreatListNode(1);
	return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而mallocfree不会。

三、operator new与operator delete函数

newdelete是用户进行动态内存申请和释放的操作符operator newoperator delete是系统提供的全局函数(不是重载!),new底层调用operator new 全局函数来申请空间(对malloc的封装),delete在底层通过operator delete全局函数来释放空间(对free的封装)。

/*
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和delete的实现原理

4.1 内置类型

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

4.2 自定义类型

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来释放空间

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

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

相关文章

大模型+多模态合规分析平台,筑牢金融服务安全屏障

随着金融市场的快速发展&#xff0c;金融产品和服务日趋多样化&#xff0c;消费者面临的风险也逐渐增加。 为保护消费者权益&#xff0c;促进金融市场长期健康稳定发展&#xff0c;国家监管机构不断加强金融监管&#xff0c;出台了一系列法律法规和政策文件。对于金融从业机构…

代码托管服务:GitHub、GitLab、Gitee

目录 引言GitHub&#xff1a;全球最大的代码托管平台概述功能特点适用场景 GitLab&#xff1a;一体化的开发平台概述功能特点适用场景 Gitee&#xff08;码云&#xff09;&#xff1a;中国本土化的代码托管服务概述功能特点适用场景 功能对比结论 引言 在现代软件开发中&#…

Pickle, SafeTensor, GGML和GGUF

如今&#xff0c;大型语言模型的使用方式有以下几种&#xff1a; 作为 OpenAI、Anthropic 或主要云提供商托管的专有模型的 API 端点作为从 HuggingFace 的模型中心下载的模型工件和/或使用 HuggingFace 库进行训练/微调并托管在本地存储中作为针对本地推理优化的格式的模型工…

机器学习 中数据是如何处理的?

数据处理是将数据从给定形式转换为更可用和更理想的形式的任务&#xff0c;即使其更有意义、信息更丰富。使用机器学习算法、数学建模和统计知识&#xff0c;整个过程可以自动化。这个完整过程的输出可以是任何所需的形式&#xff0c;如图形、视频、图表、表格、图像等等&#…

Linux基础篇——目录结构

基本介绍 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录"/"&#xff0c;然后在根目录下再创建其他的目录 在Linux中&#xff0c;有一句经典的话&#xff1a;在Linux世界里&#xff0c;一切皆文件 Linux中根目录下的目录 具体的…

PHP留守儿童关爱之家网站-计算机毕业设计源码11079

目录 1 绪论 1.1 研究背景 1.2研究意义 1.3 论文结构与章节安排 2 留守儿童关爱之家网站系统分析 2.1 可行性分析 2.2 系统功能分析 2.3 系统用例分析 2.4 系统流程和逻辑 2.5本章小结 3 留守儿童关爱之家网站总体设计 3.1系统结构设计 3.2系统功能模块设计 3.2 数…

基于SSM网上拍卖系统

设计技术&#xff1a; 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringMybatisSpringMvc 工具&#xff1a;IDEA、Maven、Navicat 主要功能 管理员功能有个人中心&#xff0c;用户管理&#xff0c;卖家管理&#xff0c;商品类型管理&#xff0c;拍卖…

EDA 虚拟机 Synopsys Sentaurus TCAD 2017.09 下载

下载地址&#xff08;制作不易&#xff0c;下载使用需付费&#xff0c;不能接受的请勿下载&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1327I58gvV1usWSqSrG7KXw?pwdo03i 提取码&#xff1a;o03i

AI网络爬虫001:用kimichat自动批量提取网页内容

文章目录 一、准备工作二、输入内容三、输出内容一、准备工作 在网页中按下F12键,查看定位网页元素 二、输入内容 在kimi中输入提示词: 你是一个Python编程专家,要完成一个爬取网页内容的Python脚本,具体步骤如下:在F盘新建一个Excel文件:提示词.xlsx打开网页:https:…

http.cookiejar.LoadError: Cookies file must be Netscape formatted,not JSON.解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【Linux】线程周边002之线程安全

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.Linux线程互斥 1…

Java实现日志全链路追踪.精确到一次请求的全部流程

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 实现效果如下: 一次查询即可找到所有关…

数据分析入门指南:从基础概念到实际应用(一)

随着数字化时代的来临&#xff0c;数据分析在企业的日常运营中扮演着越来越重要的角色。从感知型企业到数据应用系统的演进&#xff0c;数据驱动的业务、智能优化的业务以及数智化转型成为了企业追求的目标。在这一过程中&#xff0c;数据分析不仅是技术的运用&#xff0c;更是…

竹云助力雁塔城运集团实现西安市城投企业数据资产入表第一单!

近日&#xff0c;雁塔区城运集团联合陕数集团、深圳竹云科技股份有限公司等机构&#xff0c;顺利完成数据资产确权登记和数据资产入表工作&#xff0c;成为西安市首个城投数据资产入表案例&#xff0c;并获得陕西丝路数据交易中心颁发的数据资产登记证书。 近年来&#xff0c;…

使用Vue-cli脚手架创建uni-app项目(Vue2版本)

文章目录 前言准备工作接下来创建我们的 uni-app 项目 前言 uni-app官方说除了HBuilderX可视化界面&#xff0c;也可以使用 cli 脚手架&#xff0c;可以通过 vue-cli 创建 uni-app 项目。 uni-app官网文档 准备工作 需要安装 node.js 与 vue-cli 脚手架 我是用的版本如下 no…

【Python】从基础到进阶(二):了解Python语言基础以及数据类型转换、基础输入输出

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、基本数据类型转换1. 隐式转换2. 显式转换 三、基本输入输出1. 输入&#xff08;input&#xff09;2. 输出&#xff08;print&#xff09;3. 案例&#xff1a;输入姓名、年龄、身高以及体重&#xff0c;计算BMI指…

ICMAN触摸芯片之隔空感应

ICMAN触摸芯片之隔空感应 ICMAN触摸芯片满足工业级设计标准&#xff0c; 可过CS10V&#xff0c;ESD8kV&#xff0c;EFT4kV测试&#xff0c; 有超强稳定性和抗干扰能力 &#xff0c; 多用在普通触摸按键开关、大金属触摸及高灵敏度应用场合。 可根据实际应用&#xff0c;有低…

MWCSH 2024丨美格智能亮相上海世界移动通信大会,加速5G+AIoT应用进程

6月26日—28日全球通信领域最具规模和影响力的通信盛事—2024MWC上海世界移动通信大会在上海新国际博览中心隆重举行。MWC上海是亚洲连接生态系统的风向标&#xff0c;本届大会以“未来先行&#xff08;Future First&#xff09;”为主题&#xff0c;聚焦“超越5G”“人工智能经…

牛客小白月赛97 (个人题解)(待补完)

前言&#xff1a; 前天晚上写的一场牛客上比赛&#xff0c;虽然只写出了三道&#xff0c;但比起之前的成绩感觉自己明显有了一点进步了&#xff0c;继续努力吧&#xff0c; 正文&#xff1a; 链接&#xff1a;牛客小白月赛97_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞…

短信接口API的选择因素?有哪些使用方法?

短信接口API的集成难点是什么&#xff1f;如何保障API安全性&#xff1f; 短信接口API已经成为许多企业和开发者的关键工具&#xff0c;市场上有许多不同的短信接口API可供选择&#xff0c;这使得选择适合的API变得尤为重要。AoKSend将探讨在选择短信接口API时需要考虑的主要因…