【hello C++】内存管理

news2024/11/18 14:51:27

目录

前言: 

1. C/C++内存分布

2. C语言动态内存管理方式

3. C++内存管理方式

3.1 new / delete 操作内置类型

3.2 new和delete操作自定义类型

4. operator new与operator delete函数

4.1 operator new与operator delete函数

5. new和delete的实现原理

5.1 内置类型

5.2 自定义类型

6. 定位new表达式(placement-new)

malloc/free和new/delete的区别



 C++🌷

前言: 

在Windows操作系统中,我们使用VS来编写C/C++代码,那编写好的代码到底是存在哪里呢?

 

其实,不论是我们编写好的代码,还是经过编译、链接、执行,生成的目标文件还是可执行

文件都是以文件的形式存在磁盘上的。

知道了整个代码文件存在哪里,那运行起来代码中的数据、指令代码存在哪里呢?

1. C/C++内存分布

在之前的学习中,我们知道C/C++程序内存大致划分为4个区域:

1.  栈:又叫堆栈--一般存储:非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2.  堆:用于程序运行时动态内存分配,堆是可以上增长的。
3.  数据段:--一般存储:存储全局数据和静态数据。
4.  代码段:--一般存储:可执行的代码/只读常量。
知道了上述内容,我们先来看下面的一段代码和相关问题来练练手:
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 在哪里?____

	char2在哪里?____ * char2在哪里?___
	pChar3在哪里?____ * pChar3在哪里?____
	ptr1在哪里?____ * ptr1在哪里?____
2. 填空题:
	sizeof(num1) = ____;
	sizeof(char2) = ____;      strlen(char2) = ____;
	sizeof(pChar3) = ____;     strlen(pChar3) = ____;
	sizeof(ptr1) = ____;
【答案】1. C C C A  A        A A A D A B;        2. 40 5 4 4/8 4 4/8
有问题的老铁们可以评论指出,看到我会解答!

下面这张图大家也可以看一下:

在这里顺便回顾一个知识点:

3. sizeof 和 strlen 区别?

 【答】:sizeof是一个运算符,我们通常使用它来求字节数,使用方法为:sizeof()括号里面

为想要求的字节数的内容;

strlen为一个函数,我们通常使用它来求字符串的长度。

2. C语言动态内存管理方式

在C语言中我们通常使用malloc / calloc / realloc / free的方式来完成动态内存的开辟和释放。

在之前的博客中我有具体写到它们的使用及区别,大家可以看下:

C语言中的内存管理

3. C++内存管理方式

C++是兼容C语言的,C++也可以使用malloc / free等方式来完成动态内存的开辟和释放。

C++又是对C语言的一种优化,在C语言的基础上提出了自己的内存管理方式:

通过newdelete操作符进行动态内存管理

我们知道C++的数据类型分为内置类型 和 自定义类型,下面看一下它们的具体使用:

3.1 new / delete 操作内置类型

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

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

	//动态申请3个int类型的空间
	int* p3 = new int[3];

	delete p1;	//释放
	delete p2;
	delete[] p3;
}

下面看下图解:

 这里其实new和delete 与 malloc和free完成的功能差不多,都是内存的开辟和释放工作;

 但是new和delete是不是比malloc和free方便很多呢?

但这里也有非常要注意的东西:

申请和释放单个元素的空间,使用newdelete操作符;

申请和释放连续的空间,使用 new[]和delete[];

注意:匹配起来使用。

补充:

其实在C++11中支持 new[] 用{}初始化;

但在C++98中,这是不支持的。

3.2 new和delete操作自定义类型

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A():" << this << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
    // new/delete 和 malloc/free最大区别是:
    // new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	cout << endl << endl;
	A* p2 = new A(1);
	free(p1);
	cout << endl << endl;
	delete p2;

    // 内置类型是几乎是一样的
	int* p3 = (int*)malloc(sizeof(int));
	int* p4 = new int;
	free(p3);
	delete p4;
	cout << endl << endl;

	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;
	return 0;
}

new对于自定义类型自动调用它的构造;

delete对于自定义类型自动调用它的析构。 

 

 new和delete对于内置类型同malloc和free功能类似:

 总结:

在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc
free 不会
在申请内置数据类型的空间时,new/delete 和 malloc/free 完成的功能类似;

4. operator newoperator delete函数

4.1 operator newoperator delete函数

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

注意:

operator new只是完成了开辟空间的操作,

new:开辟空间+构造函数(自定义类型)构造; 

 这个链接很重要:一定要看!!!

operator new 和 new的关系

下面的内容了解即可:

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

5. newdelete的实现原理

5.1 内置类型

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

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;

int main()
{
	//malloc失败返回空,需要检查
	char* p1 = (char*)malloc(1024 * 1024 * 1024);
	if (p1 == NULL)
		perror("malloc");
	printf("%p\n", p1);
	//new失败抛出异常,不要检查返回值
	try
	{
		char* p2 = new char[1024*1024*1024];
		printf("%p\n", p2);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

上述抛出及捕获异常的方法,大家需要记一下;

如果57行抛出异常,那么不用执行后续代码,直接跳到60行执行。

补充:

每个类可以去实现自己专属operator new  new这个类对象,他就会调自己实现这个operator new

// 每个类可以去实现自己专属operator new  new这个类对象,他就会调自己实现这个operator new

// 实现一个类专属的operator new  -- 了解一下
struct ListNode
{
	int _val;
	ListNode* _next;

	// 内存池
	static allocator<ListNode> alloc;

	void* operator new(size_t n)
	{
		cout << "operator new -> STL内存池allocator申请" << endl;
		void* obj = alloc.allocate(1);
		return obj;
	}

	void operator delete(void* ptr)
	{
		cout << "operator delete -> STL内存池allocator申请" << endl;

		alloc.deallocate((ListNode*)ptr, 1);
	}

	struct ListNode(int val)
		:_val(val)
		, _next(nullptr)
	{}
};

// allocator以后会讲,现在先会用即可
allocator<ListNode> ListNode::alloc;

int main()
{
	// 频繁申请ListNode. 想提高效率 -- 申请ListNode时,不去malloc,而是自己定制内存池
	ListNode* node1 = new ListNode(1);
	ListNode* node2 = new ListNode(2);
	ListNode* node3 = new ListNode(3);

	delete node1;
	//delete node2;
	delete node3;

	A* p1 = new A;

	return 0;
}

operator new是一个封装好的函数,完成new的整个功能,其实内部也是通过malloc申请空间

的,不过在此基础之上,完成了对malloc申请失败后抛出异常的处理,使得new申请失败后

会抛出异常。

在使用new时,调用全局的operator new 和 类专属的operator new的区别:

调用全局的:每次调用每次向堆区申请空间,效率不高;

调用类专属的:有一个内存池,每次申请时申请内存池里面的内存空间,内存池没有了,一

下向堆区申请多个空间存在内存池里,再从内存池申请空间,效率较高;

5.2 自定义类型

1. new 的原理
1. 调用 operator new 函数申请空间;
2. 在申请的空间上执行构造函数,完成对象的构造;
2. delete 的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作;
2. 调用 operator delete 函数释放对象的空间
3. new T[N] 的原理
1. 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间
的申请;
2. 在申请的空间上执行 N 次构造函数;
4. delete[ ] 的原理
1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理;
2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间

6. 定位new表达式(placement-new)

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

  定位new表达式在实际中一般是配合内存池使用。
因为内存池分配出的内存没有初始化,
所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。    
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

// 定位new/replacement new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));

	// 注意:如果A类的构造函数有参数时,此处需要传参
	new(p1)A; 
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

总结:

对于自定义类型:

new:开辟空间+构造;

也就是说:

operator new 与 定位new 联合起来就是new的整个功能。

malloc/freenew/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/445086.html

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

相关文章

杭州乐芯CNC 机器人采集方法汇总

杭州乐芯CNC数据采集方法及支持的系统 一&#xff0c;杭州乐芯CNC 机器人采集方法汇总 1&#xff0c;采用SDK开发包采集&#xff0c;比如发那科 FANUC \三菱 、海德汉、大隈OKUMA&#xff08;O-API&#xff09;、华中数控、凯恩帝、沙迪克、牧野电火花、台湾宝元、上海来钠克…

怎么把avi文件转换成mp4视频格式,4个高能方法

怎么把avi文件转换成mp4视频格式&#xff1f; 当您下载到avi格式的视频文件时&#xff0c;您可能会选择将其转换为MP4格式的文件。 avi是一种由微软开发的多媒体容器格式&#xff0c;尽管现在已经被认为是老旧的技术&#xff0c;但由于其简单易懂的开发API和Windows的通用性&am…

【CSS3】CSS3 2D 转换 - rotate 旋转 ② ( 使用 rotate 旋转绘制三角形 )

文章目录 一、使用 rotate 旋转绘制三角形二、代码示例 一、使用 rotate 旋转绘制三角形 使用 rotate 旋转绘制三角形 的原理 : 先绘制正方形 , 为该正方形设置边框 , 只设置 右侧 和 下方的 边框 , div {width: 40px;height: 40px;border-right: 2px solid black;border-bott…

tmux---linux终端复用器使用

tmux---linux终端复用器使用 tmux 介绍安装使用tmux会话管理新建会话分离会话接入会话杀死会话切换会话重命名会话 窗格操作划分窗格移动光标交换窗格位置窗格快捷键 tmux 介绍 命令行的典型使用方式是&#xff0c;打开一个终端窗口&#xff08;terminal window&#xff0c;以…

发令枪音效芯片,语音提示ic

发令枪属于管制物品&#xff0c;只能在运动员训练或比赛时使用&#xff0c;其余时间段不能使用&#xff0c;更不能在非比赛场地公众场合使用。在径赛、游泳比赛等开始时&#xff0c;主要是用来发出声音信号的器械&#xff0c;形状像手枪。 电子发令枪&#xff0c;采用电子元器…

Baklib最佳实践:如何设计符合用户需求的FAQ页面?

在当今互联网时代&#xff0c;随着信息量的不断增加和用户信息获取方式的多样化&#xff0c;越来越多的公司、组织和个人开始创建自己的FAQ页面。作为一个企业或组织的“门面”&#xff0c;FAQ页面对于提升品牌形象、提高服务质量以及吸引潜在客户等方面都起着至关重要的作用。…

传统机器学习(三)聚类算法K-means(一)

传统机器学习(三)聚类算法K-means(一) 一、聚类算法K-means初识 1.1 算法概述 K-Means算法是无监督的聚类算法&#xff0c;它实现起来比较简单&#xff0c;聚类效果也不错&#xff0c;因此应用很广泛。K-Means基于欧式距离认为两个目标距离越近&#xff0c;相似度越大。 1.…

详解C语言string.h中常见的14个库函数(二)

本篇博客继续讲解string.h中的库函数。在上一篇博客中&#xff0c;我介绍了strlen, strcpy, strcat, strcmp这4个字符串操作函数&#xff0c;本篇博客会继续介绍strncpy, strncat, strncmp这3个类似的函数。 strcpy, strcat, strcmp这3个函数是长度不受限制的字符串操作函数&a…

ubuntu安装V2board宝塔面板教程

ubuntu安装V2board宝塔面板教程 运行环境:ubuntu-20.04 搭建宝塔web页面环境 切到linux服务器命令行 在用户目录下创建BT目录,进入BT目录 在BT目录下执行命令 sudo wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo sh install.sh…

考虑梯水电站群的水火电节能调度(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

oracle rac 安装文档

oracle rac 安装文档 一、前期准备1.1 操作系统centos 7.4 *2台&#xff0c;要求双网卡1.2 主机名&#xff1a;oracle01&#xff0c;oracle021.3 数据库版本&#xff1a;oracle 11g rac1.4 空间要求&#xff1a;1.4.1 系统&#xff1a;&#xff08;系统使用磁盘&#xff09;1.4…

python基于mediapipe开发构建人体姿态检测系统

之前做的人体姿态检测大都是基于openpose或者是yolo-pose之类的技术框架&#xff0c;这里主要是想基于一个开源的实现来完成人体姿态检测。首先看下效果图&#xff1a; Mediapipe是google的一个开源项目&#xff0c;支持跨平台的常用ML方案。项目在这里&#xff0c;如下所示&am…

软件挖掘|软件缺陷|蜕变关系

目录 软件无处不在 软件发展不是一个简单的任务 识别软件缺陷 软件缺陷定位 使用bug报告识别有缺陷的源代码 使用标准IR方法建模相关性 VSM rVSM 根据bug报告和源代码学习共享主题&#xff0c;并根据主题衡量相关度 学习结合大量手工制作的特性 如何建模&#xf…

页表结构详细说明

一、页表 1. 内存地址的分解 我们知道linux采用了分页机制&#xff0c;通常采用四级页表&#xff0c;页全局目录(PGD)&#xff0c;页上级目录(PUD)&#xff0c;页中间目录(PMD)&#xff0c;页表(PTE)。如下&#xff1a; 其含义定义在arch/arm64/include/asm/pgtable-hwdef.…

应急响应 - Windows启动项分析,Windows计划任务分析,Windows服务分析

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 Windows应急响应 一、启动项分析1、msconfig2、gpedit.msc3、注册表4、msinfo325、启动菜…

【数据结构实验】哈希表设计

【数据结构实验】哈希表设计 简介 针对本班同学中的人名设计一个哈希表&#xff0c;使得平均查找长度不超过R&#xff0c;完成相应的建表和查表程序。文末贴出了源代码。 需求分析 假设人名为中国人姓名的汉语拼音形式&#xff0c;待填入哈希表的人名共有三十个左右&#x…

汇编指令角度掌握函数调用堆栈详细过程

函数参数从右向左依次压栈push&#xff0c;call 要先把下一行指令 压栈&#xff0c; 根据如下代码思考两个问题&#xff1a; 问题一&#xff1a;main函数调用sum&#xff0c;sum执行完以后&#xff0c;怎么知道回到哪个函数中&#xff1f; 问题二&#xff1a;sum函数执行完&…

使用树状图可视化聚类

一般情况下&#xff0c;我们都是使用散点图进行聚类可视化&#xff0c;但是某些的聚类算法可视化时散点图并不理想&#xff0c;所以在这篇文章中&#xff0c;我们介绍如何使用树状图&#xff08;Dendrograms&#xff09;对我们的聚类结果进行可视化。 树状图 树状图是显示对象…

vue 项目中 向数组 添加元素 的方式

学习目标&#xff1a; 提示&#xff1a;了解 vue 向数组 添加元素 的三种方式 学习内容&#xff1a; 提示&#xff1a; vue 向数组 添加元素 的三种方式 方法&#xff1a; push()unshift()splice() 总结&#xff1a; 提示&#xff1a;这里总结相关的知识 1、push() 含义&a…

网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

文章目录 1. 什么是网络编程2. 网络编程中的基本概念1&#xff09;发送端和接收端2&#xff09;请求和响应3&#xff09;客户端和服务端4&#xff09;常见的客户端服务端模型 3. Socket 套接字1&#xff09;Socket 的分类2&#xff09;Java 数据报套接字通信模型3&#xff09;J…