C++笔记:动态内存管理

news2025/2/21 23:38:35

文章目录

  • 语言层面的内存划分
  • C语言动态内存管理的缺陷
  • new 和 delete 的使用了解
    • 语法
    • new 和 delete 操作内置类型
    • new 和 delete 操作自定义类型
  • new 和 delete 的细节探究
  • new 和 delete 的底层探究
    • operator new 和 operator new[]
    • operator delete 和 operator delete[]
  • 显式调用构造函数:定位new
  • malloc/free和new/delete的区别

语言层面的内存划分

想要全面认识动态内存管理就要对 C/C++ 语言层面上的内存划分有一个简单的认识。

第一点:内存整体被划分为了多个区域,每个区域有各自的特点。
在这里插入图片描述

我们常遇到的有四个区域:栈区、堆区、数据段(静态区)、代码段(常量区)。

  1. 【栈区】
    又称 “ 堆栈”,栈内存的分配和释放是由处理器的指令集中的特定指令来执行的,由于硬件支持,所以效率很高,但是分配给栈的内存空间容量有限,并且是在编译时被编译器确定的,使用时超出容量限制就会引发 “ 栈溢出 ” 错误。
    栈区主要存放运行函数而分配的局部变量、函数参数、函数返回值等(主要是临时需求)。

  2. 【堆区】
    堆区内存容量比栈区大得多,而且堆区内存空间的分配和释放由程序员手动控制,如果程序员不手动释放,只会由操作系统在程序结束时释放;堆区内存空间的访问速度相对于栈区是比较慢的,主要用来满足程序员按需使用内存空间的需求(如算法、数据结构),堆区内存的分配与释放又被称为 “ 动态内存管理 ”

  3. 【数据段】
    数据段是用来存储全局变量、静态变量的数据的,该部分内存空间在程序结束后由操作系统释放。

  4. 【代码段】
    该区域是用来存放可执行代码和只读常量的。可执行代码指的不是我们写的 C/C++ 代码(硬盘),而是经过编译器编译后生成的二进制代码;只读常量,也叫 “ 字面值常量 ”,程序中有着相当多的数据,有一部分是被指定好了的,不希望被修改的数据,这部分内存空间就是用来存放这部分数据的。

第二点:内存区域具体如何划分取决于操作系统如何设计。

这里个人用了一段代码来验证上面提出的内存分布模型是否真实。

#include <iostream>
using namespace std;

int globalVal = 1;
int globalStaticVal = 1;

int main()
{
	static int staticVal = 1;

	int* ptr1 = (int*)malloc(sizeof(int) * 5);
	int* ptr2 = (int*)malloc(sizeof(int) * 5);
	int* ptr3 = (int*)malloc(sizeof(int) * 5);
	int* ptr4 = (int*)malloc(sizeof(int) * 5);
	int* ptr5 = (int*)malloc(sizeof(int) * 5);

	const char* pstr = "abcd";

	cout << "栈区:" << endl;
	printf("&ptr1: %p\n", &ptr1);
	printf("&ptr2: %p\n", &ptr2);
	printf("&ptr3: %p\n", &ptr3);
	printf("&ptr4: %p\n", &ptr4);
	printf("&ptr5: %p\n\n", &ptr5);


	cout << "堆区:" << endl;
	printf("ptr5: %p\n", ptr5);
	printf("ptr4: %p\n", ptr4);
	printf("ptr3: %p\n", ptr3);
	printf("ptr2: %p\n", ptr2);
	printf("ptr1: %p\n\n", ptr1);

	cout << "数据段:" << endl;
	printf("&globalVal: %p\n", &globalVal);
	printf("&globalStaticVal: %p\n", &globalStaticVal);
	printf("&staticVal: %p\n\n", &staticVal);
	
	cout << "代码段:" << endl;
	printf("pstr: %p\n", pstr);
	
	return 0;
}

下面是这份代码分别在【Linux】和【Windows】下的测试结果:

在这里插入图片描述

通过对比可以看到,Linux 下是完全符合上面提到的内存分布结构,但是 Windows 下就不太一样了,所以上面那张图片仅供参考,但是能够确定的是,操作系统确实会对内存结构进行分区,但是具体如何分区那就得看操作系统本身是如何设计的了

C语言动态内存管理的缺陷

C语言中提供了以下函数来进行动态内存管理:

// 向堆区申请一块连续的内存空间
void* malloc (size_t size);
// 向堆区申请一块连续的内存空间,并初始化为0
void* calloc (size_t num, size_t size);
// 重新调整申请的内存空间的大小
void* realloc (void* ptr, size_t size);
// 释放申请的连续空间
void free (void* ptr);

C++ 是在C语言的基础上发展来的而且C++也完全兼容C语言的动态内存管理方式,但是随着C++的发展,特别是 “ 类与对象 ” 的引入之后,C语言动态内存管理的方式在有些就显得无能为了,而且使用起来比较麻烦。

以下面这个简单例子来进行一个演示:
在这里插入图片描述
代码中使用 malloc 函数向内存申请了一个块大小为 sizeof(A) 空间,然后用指针变量 p1 来接收空间的地址,然后问题来了,我们该如何初始化这块空间?

首先,从对比来看,mallocfree 是不会调用构造函数和析构函数的;同时,C++语法也说明了构造函数是实例化对象时由编译器自动调用的,无法通过 p1->A() 的方式直接显式调用。

其次,直接访问成员(p1->_a)这一方法也行不通,类的成员为私有属性,无法直接访问,而且类成员设置为私有属性本身就是为了数据安全所考虑的,改成公有就有点本末倒置。

这么看下来,好像就只能额外的设计两个函数分别用来代替构造和析构,但是这么设计的话构造和析构的语法设计就显得没有价值了。因此,可以看到 malloc 和 free 不方便解决C++动态申请的自定义类型对象的初始化问题

为了解决这一缺陷,C++设计了新的动态内存管理的方式,即 newdelete

new 和 delete 的使用了解

语法

new 用于在动态内存中分配一个对象,并返回对象的指针

// 分配单个对象
type* pointer = new type;

// 分配对象数组
type* arrayPointer = new type[size];

注意:
new 会分配一个单独的对象,并返回指向该对象的指针,new[] 会分配一个对象数组,并返回指向第一个对象的指针。

delete用于释放通过new分配的对象或对象数组

// 释放单个对象
delete pointer;

// 释放对象数组
delete[] arrayPointer;

注意:
delete 用于释放 new 申请的空间,delete[] 用于释放 new[] 申请的空间,二者一定要配对使用,不然容易出现错误。

new 和 delete 操作内置类型

newdelete 对于内置类型对象的申请与释放上与 mallocfree 没有任何区别,以整型为例,其他类型操作也是一样的:

#include <iostream>
using namespace std;

int main()
{
	// 动态申请一个int类型的空间
	int* p1 = new int;
	
	// 动态申请一个int类型的空间并初始化为10
	int* p2 = new int(10);
	
	// 动态申请10个int类型的空间
	int* p3 = new int[10];

	// 动态申请10个int类型的空间并初始化前3个元素
	int* p4 = new int[10]{ 1, 2, 3 };
	
	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;

	return 0;
}

new 和 delete 操作自定义类型

在自定义类型上,new 的作用为:先申请空间 + 后调用构造;delete 的作用为:先调用析构 + 后释放空间。

new 调用构造有三种方式,最方便的是第三种:
在这里插入图片描述

new 和 delete 的细节探究

#include <iostream>
using namespace std;

class stack
{
public:
	stack(int capacity = 4)
	{
		_a = new int[capacity];
		_size = 0;
		_capacity = 4;
	}

	~stack()
	{
		delete[] _a;
		_size = _capacity = 0;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};

int main()
{
	stack* p1 = new stack;
	delete p1;
	
	stack* p2 = new stack[10];
	delete[] p2;
	return 0;
}

new/delete 单个对象的详细过程:
在这里插入图片描述

new/delete 对象数组的详细过程:
在这里插入图片描述

从上面的过程分析中也说明了,为什么 new-deletenew[]-delete[] 一定要配对使用,否则可以会因为找不到空间正确的起始地址而引发错误。

new 和 delete 的底层探究

new 和 delete 是用户进行动态内存申请和释放的操作符

  • new 在底层调用 operator new 全局函数来申请空间,new[] 在底层调用 operator new[] 全局函数来申请空间;
  • delete 在底层通过 operator delete 全局函数来释放空间,delete[] 在底层通过 operator delete[] 全局函数来释放空间;

operator new 和 operator new[]

这是一个C++标准库中 operator new 的示例实现:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// 尝试分配 size 字节的内存
	void* p;
	while ((p = malloc(size)) == 0)
	// 如果分配失败,尝试调用 new_handler 函数处理,即_callnewh
	if (_callnewh(size) == 0)
	{
		// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
		static const std::bad_alloc nomem;
		_RAISE(nomem);
	}
	return (p);
}

需要注意的是,这段代码中包含了一些平台特定的宏和定义,例如__CRTDECL_THROW1_RAISE等。这些宏和定义可以在特定的编译环境中提供一些额外的功能或兼容性支持。

虽然这个函数实现的细节个人目前也没有全部搞懂(异常处理还未学习),但是有一点可以肯定的就是 operator new 复用了 malloc,也就是说,operator new 实际上是通过调用 malloc 来向内存申请空间的,如果空间申请失败,就进行异常处理。

operator new[] 则是对 operator new 的再封装:

void* operator new[](size_t size) THROW1(std::bad_alloc)
{
    // 调用 operator new 来分配内存
    return operator new(size);
}

operator delete 和 operator delete[]

如果说 operator new 封装了 malloc 那么 operator delete 就是封装了 free

// free的实现
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

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;
}

在C语言的底层,free 本质上是一个宏函数,而 operator delete 最终是通过 free 来释放空间的(虽然除了调用 free 外还进行了很多操作)。

operator new[] 类似,operator delete[] 也是对 operator delete 的再封装。

以上的代码示例仅供参考,函数的具体实现依据依据平台而定。

显式调用构造函数:定位new

关于构造函数和析构函数,其实有个很特别的现象:

stack* p = (stack*)malloc(sizeof(stack));
// error: p->stack();
p->~stack();

指针变量p无法直接显式调用构造函数却能够显式调用析构函数,是不是很神奇?

根据这个现象来猜测,C++中应该有某种方式来显式调用构造函数,这个设计就是 “ 定位new ”。

【定位new表达式的作用】

在已分配的原始内存空间中调用构造函数初始化一个对象。

【定位new表达式的语法格式】

// 1
new (place_address) type
// 2
new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

【定位new表达式的使用场景】

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

关于内存池,目前了解不多,所以就不写在笔记里了。

【new 和 delete 的再认识】

#include <iostream>
using namespace std;

class stack
{
public:
	stack(int capacity = 4)
	{
		_a = new int[capacity];
		_size = 0;
		_capacity = 4;
	}

	~stack()
	{
		delete[] _a;
		_a = nullptr;
		_size = _capacity = 0;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};

int main()
{
	// operator new + 定位new -> 承担 new 的作用
	stack* p = (stack*)operator new(sizeof(stack));
	new(p)stack(10);	// 如果构造有参数需要传参
	
	// 析构 + operator delete -> 承担 delete 的作用
	p->~stack();
	operator delete(p);

	return 0;
}

malloc/free和new/delete的区别

【共同点】

  • 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在释放空间前会调用析构函数完成空间中资源的清理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023快速上手新红利项目:短剧分销推广CPS

短剧分销推广CPS是一个新红利项目&#xff0c;对于新手小白来说也可以快速上手。 以下是一些建议&#xff0c;帮助新手小白更好地进行短剧分销推广CPS&#xff1a; 学习基础知识&#xff1a;了解短剧的基本概念、制作流程和推广方式。了解短剧的市场需求和受众群体&#xff0c…

wpf devexpress如何使用AccordionControl

添加一个数据模型 AccordionControl可以被束缚到任何实现IEnumerable接口的对象或者它的派生类&#xff08;例如IList,ICollection&#xff09; 如下代码例子示范了一个简单的数据模型使用&#xff1a; using System.Collections.Generic;namespace DxAccordionGettingStart…

zabbix精简模板

一、监控项目介绍 linux自带得监控项目比较多&#xff0c;也不计较杂&#xff0c;很多监控项目用不到。所以这里要做一个比较精简得监控模版 二、监控模板克隆 1.搜索原模板 2.克隆模板 全克隆模板&#xff0c;这样就和原来原模板没有联系了&#xff0c;操作也不会影响原模…

软件测试基础知识+面试总结(超详细整理)

一、什么是软件&#xff1f; 软件是计算机系统中的程序和相关文件或文档的总称。 二、什么是软件测试&#xff1f; 说法一&#xff1a;使用人工或自动的手段来运行或测量软件系统的过程&#xff0c;以检验软件系统是否满足规定的要求&#xff0c;并找出与预期结果之间的差异…

UI设计中的肌理插画是什么样的?

肌理插画本质也和扁平插画差不多&#xff0c;相较扁平插画&#xff0c;肌理插画的层次感、细节更多&#xff0c;也会更立体生动。 肌理插画风格没有描边线&#xff0c;画面轻快&#xff0c;通过色块的明暗来区分每个元素&#xff0c;有点像色彩版的素描&#xff0c;但更简单&a…

一个人全干!之后台管理中的搜索区域的展开收缩组件。

后台管理系统中大多数都有列表的搜索&#xff0c;那么用户的需求又需要必要时收缩搜索区域&#xff0c;需要时再展开。 而且怪的是他还需要一些部分不可收缩&#xff0c;不需要的地方才收缩。使用v-if来解决吧又不咋美观&#xff0c;我们还需要一个简单的动画效果。我们先写一…

橘子学K8S02之容器中所谓的限制

前面我们知道了关于隔离在Linux中的实现是通过一组NameSpace做到的&#xff0c;而且实际上他只是修改了应用进程看到计算机的视图&#xff0c;对他的视野做了限制&#xff0c;只能看到某些特定的内容&#xff0c;但是当你把视角切换到宿主机的操作系统来看的时候&#xff0c;这…

JIT即时编译器深度解析——Java性能提升利器

文章目录 一、JIT概述1、为什么要用JIT即时编译器2、C1、C2与Graal编译器3、分层编译4、热点代码5、热点探测&#xff08;1&#xff09;方法调用计数器&#xff08;2&#xff09;回边计数器 二、编译优化技术1、方法内联&#xff08;1&#xff09;什么是方法内联&#xff08;2&…

银行数据分析指标篇:最全银行数据指标体系打包送给你!

前两天分享了银行业数据分析的案例&#xff0c;今天呢&#xff0c;老李把金融行业的指标体系和典型分析场景完整分享给大家&#xff01;做地通俗易懂&#xff0c;条理清晰&#xff0c;很快就能上手。 银行指标体系 “指标”作为业务和数据的结合&#xff0c;它使得业务目标可…

Vue 2.0源码分析-update

Vue 的 _update 是实例的一个私有方法&#xff0c;它被调用的时机有 2 个&#xff0c;一个是首次渲染&#xff0c;一个是数据更新的时候&#xff1b;由于我们这一章节只分析首次渲染部分&#xff0c;数据更新部分会在之后分析响应式原理的时候涉及。_update 方法的作用是把 VNo…

聚焦本田XR-V和福特领睿:两大SUV综合实力对比,谁更胜一筹?

在当今的SUV市场中&#xff0c;家庭用户的选择变得越来越多样化。特别是对于那些追求时尚、功能性以及技术先进性的用户来说&#xff0c;选择正确的SUV显得尤为重要。本文将重点对比福特领睿和本田XR-V这两款SUV&#xff0c;探讨它们在各方面的表现&#xff0c;做一个综合实力的…

艾江山:你好好养生,我带你去看海

有多少人&#xff0c;还没好好看过这个世界&#xff1b; 有多少人&#xff0c;因为不够健康缺少走出来的勇气。 艾江山第二届平凡人的养生故事大赛暨北海游学&#xff0c;在一片依依不舍中圆满闭幕。 游学博闻&#xff0c;盖谓其因游学所以能博闻也 传统上&#xff0c;游学是…

Power BI - 5分钟学习增加条件列

每天5分钟&#xff0c;今天介绍Power BI增加条件列。 什么是增加条件列&#xff1f; 简单理解&#xff0c;可以根据表中某列设置一个或者多个条件&#xff0c;判定的结果会生成一个新列。 举例&#xff1a; 首先&#xff0c;导入一张【Sales】样例表(Excel数据源导入请参考每…

将自己的django项目部署到云服务器(腾讯云centos)

最近自己买了个云服务玩&#xff0c;突然就想把自己写的小项目部署到云服务器上&#xff0c;这样就可以实现公网访问了。以下是整个部署过程和遇到的各种问题的解决方案&#xff0c;有想自己部署自己功能的&#xff0c;可以参考着进行哦。 1、设置好腾讯云的远程登录代码 先给…

智能座舱架构与芯片- (9) 音频篇 上

一、音频总线 音频是智能座舱的核心功能&#xff0c;涵盖车载音响、语音识别、e-Call、消噪及回声消除等应用&#xff0c;随着汽车智能网联化的发展&#xff0c;对音频的开发要求也越来越高。传统的车载音频系统采用模拟并行音频信号传输方式&#xff0c;难以在功能增加与整车…

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-1开环系统与闭环系统Open/Closed Loop System

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-1开环系统与闭环系统Open/Closed Loop System EG1: 烧水与控温水壶EG2: 蓄水与最终水位闭环控制系统 EG1: 烧水与控温水壶 EG2: 蓄水与最终水位 h ˙ q i n A − g h A R \dot{…

总结了人工智能领域,能源领域,电气领域比较好中的一些sci期刊!!仅供参考

文章目录 前言一、总结了人工智能领域&#xff0c;能源领域&#xff0c;电气领域比较好中的一些sci期刊 总结 前言 期刊查询网站&#xff1a; https://www.letpub.com.cn/index.php?pagejournalapp&viewsearch 链接: 点我跳转期刊查询网站 一、总结了人工智能领域&#…

许战海战略文库|美国品牌实践:从品类品牌向产业品牌转变

引言&#xff1a;《品类战略》是上世纪70年代特劳特和里斯所推崇的定位理论,强调“品类聚焦是唯一正确的战略“新品类要使用新品牌”等战略思想,并对品牌延伸等多元化品牌进行批判,并由中国代理人传入中国&#xff0c;从2002年至今滋生了众多品类品牌,阻碍中国经济发展。 在今天…

图生视频AI技术,1张图零提示词,让静态照片动起来

AI时代的发展速度比我们想象中的快多了&#xff0c;当大部分人刚学会AI生成图片时&#xff0c;现在又开始流行AI生成视频了&#xff0c;正式从图片、文字升级到短视频时代。 最近一段时间&#xff0c;AI生成视频的技术正在突飞猛进。Pika、Runway等大家熟知的海外工具都在不断…

android studio flutter启动一直卡在“ Running gradle task ‘assembledebug‘ “的解决问题

解决问题&#xff1a; 1、修改项目中android/build.gradle文件 将buildscript.repositories下面的 //google() //mavenCentral()注释掉&#xff0c;改成maven {allowInsecureProtocol trueurl https://maven.aliyun.com/repository/google } maven {allowInsecureProtocol t…