learn C++ NO.7——C/C++内存管理

news2024/11/24 10:56:28

引言

现在是5月30日的正午,图书馆里空空的,也许是大家都在午休,也许是现在37摄氏度的气温。穿着球衣的我已经汗流浃背,今天热火战胜了凯尔特人,闯入决赛。以下克上的勇气也激励着我,在省内垫底的大学中,我不觉得气馁,我要更加努力学习,让自己能够越来越好,以后肯定也会”晋级决赛”。

1.C/C++程序的内存分布

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

2.变量在内存中存储的位置

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

在这里插入图片描述
下面我按照从左往右从上到下的顺序依次分析上面的题目。globalVar和staticGlobalVar都是定义在全局域中,所以是存储在静态区中的。staticVar虽然是在局部域内定义的,但是它是static修饰的变量所以依旧是存储在静态区中的。localVar和num1都是在局部域内定义的局部变量,都是存储在栈区空间的。上面第一部分比较简单,下面我以画图加分析的来看下面部分。
为什么char2和*char2都在局部域呢?
在这里插入图片描述
pChar3是一个定义在栈区的const char类型指针,它保存的是常量字符串"abcd"的首元素地址。*pChar3是对常量字符串的首元素地址进行解引用操作,访问的是常量区的空间。ptr1是一个在栈区上创建的指针变量,存放的是动态开辟空间首字节地址。解引用访问ptr1访问的是堆区空间。

数组名单独放在sizeof内部表示整个数组,所以是4*10字节。"abcd"其实是隐含了’\0’字符,所以是4+1=5字节。无论什么指针都是4 or 8字节,指针的大小取决于平台,64位平台8字节,32位平台4字节。

3.C语言的动态内存管理

常见的C语言动态内存管理如下:malloc/calloc/realloc/free。具体细节可以移步c语言动态内存管理,查看这里不做赘述。

void Test ()
{
	int* p1 = (int*) malloc(sizeof(int));
	free(p1);
	// 1.malloc/calloc/realloc的区别是什么?
	int* p2 = (int*)calloc(4, sizeof (int));
	int* p3 = (int*)realloc(p2, sizeof(int)*10);
	// 这里需要free(p2)吗?
	free(p3 );
}

1.malloc就是单纯地开辟堆区空间。calloc是可以指定初始化内容开辟堆区空间。realloc就是动态调整堆区申请的空间。当调整后的空间无法在原空间后扩容,则会将原空间的内容拷贝到新的空间上再申请到连续空间。
在这里插入图片描述

4.C++的动态内存管理

因为C++是兼容C语言的,所以C语言的动态内存管理依旧是可以在C++中使用的。由于C语言的动态管理方式存在缺陷,所以C++也提供了两个操作符来进行动态内存管理。分别是new 和 delete。

4.1.new和delete管理内置类型

int main()
{
	//c
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);

	//cpp
	//new后面跟的是类型
	int* p2 = new int;
	delete p2;
	
	//malloc单纯开辟空间
	//new支持初始化
	int* p3 = new int(10);
	delete p3;

	int* p4 = (int*)malloc(sizeof(int) * 10);
	free(p4);

	int* p5 = new int[10];
	delete[] p5;//一定要带[]

	//开辟连续空间也是支持初始化的
	int* p6 = new int[10]{1,2,3,4};
	delete[] p6;

}

需要注意的是使用malloc申请的内存就用free来释放,使用new申请的内存,就用delete来清理。不可以free来释放new的空间,或者用delete来释放malloc的空间。虽然在内置类型中,可能不会出问题,但是,这样使用是不规范也不正确的。这就好比吃面要用筷子,吃炸鸡要用手套,吃炒饭用勺子。你用free去释放new的内存,就好比用勺子去吃炸鸡,这样是不合适的。在某些场景下,你用free去释放new的内存就会好比用手套去吃火锅,那肯定是不行的。

4.1.new和delete操作自定义类型

struct ListNode
{
	//c++写法
	ListNode(int x)
		:_next(nullptr)
		, _val(x)
	{}

	int _val;
	struct ListNode* _next;
};

//c语言写法
struct ListNode* BuyNode(int x)
{
	struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->_next = NULL;
	newnode->_val = x;

	return newnode;
}

int main()
{
	struct ListNode* n1 = BuyNode(1);
	struct ListNode* n2 = BuyNode(2);
	struct ListNode* n3 = BuyNode(3);
	free(n1);
	free(n2);
	free(n3);

	//struct升级成了类
	ListNode* nn1 = new ListNode(1);
	ListNode* nn2 = new ListNode(2);
	ListNode* nn3 = new ListNode(3);
	delete nn1;
	delete nn2;
	delete nn3;

	return 0;
}

new可以去调用自定义类型的构造函数来初始化对象,这样写比c语言的写法香多了。

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

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A) * 4);
	A* p2 = new A[4]{1,2,3,4};
	free(p1);
	delete[] p2;
	return 0;
}

在这里插入图片描述
从上面样例可以看到,new自定义类型对象的时候,会去调用它的构造函数。当然这里是以整型值初始化自定义类型,会产生隐式类型转换,因为是在同一行编译器自动进行了优化,所以没有调用拷贝构造函数。而malloc只是单纯地开空间。delete自定义类型对象时,会去调用他的析构函数,而free只是单纯释放空间。

4.1.c++和c语言在开辟动态内存时失败处理细节

在C语言中,当使用malloc()函数开辟动态内存时,如果内存不足或者没有足够的连续空间,函数将返回NULL指针,表示内存分配失败。而在C++中,使用new操作符开辟动态内存时,如果内存不足或者没有足够的连续空间,将抛出一个std::bad_alloc异常,表示内存分配失败。

//C语言
int main()
{
	int* p1 = (int*)malloc(sizeof(int) * 1024);
	if (p1 == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	return 0;
}

//C++
int main()
{
	int* p1 = nullptr;
	try
	{
		do
		{
			p1 = new int[1024];
			cout << p1 << endl;
		} while (p1);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

在面向对象的编程语言的编程中,try-catch是一种异常处理机制。在try块中,我们编写可能引发异常的代码。如果在try块中的代码引发了异常,程序会立即跳转到与其对应的catch块。catch块定义了异常处理程序,它会处理try块中引发的异常。这里稍微了解即可,具体细节得等到后面学习后再和大家介绍了。

5.new和delete的原理

5.1.operator new与operator delete函数的介绍

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

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 new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

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 delete:通过调用free来释放空间,而且operator delete和宏函数free的底层都是调用_free_dbg来进行释放空间的

5.2.operator new、new、operator delete和delete函数的底层实现

首先,通过下面代码的汇编代码来看看operator new、new、operator delete和delete函数究竟是怎么样的吧。

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

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
	}

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

int main()
{
	A* p1 = (A*)operator new(sizeof(A));
	A* p2 = new A(1,1);
	operator delete(p1);
	delete p2;

	return 0;
}

在这里插入图片描述
在这里插入图片描述
通过查看汇编代码我们可以看到,new的会先调用operator new开辟空间,然后在调用构造函数初始化。delete会先调用析构函数进行清理,然后调用operator delete来释放动态内存。当然用new 自定义类型[N] 个对象,会先调用operator new开辟空间,然后在调用N次构造函数初始化。delete[] 会先调用N次析构函数,然后释放动态内存。

在这里插入图片描述

下面通过一个比较复杂的场景来看一下new和delete对于自定义类的的处理

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int       _capacity;
	int       _size;
};

int main()
{
	Stack* p1 = new Stack;
	delete p1;

	return 0;
}

在这里插入图片描述

6.定位new

定位new是new关键字的另一种用法,用于给已经分配好的堆区空间进行调用构造函数来初始化对象。

6.1.代码样例演示

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

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
	}

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

int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();//显示调用析构函数
	free(p1);
	return 0;
}

在这里插入图片描述

6.2.定位new的应用场景

在一些需要频繁申请堆区内存的程序中,通常需要提前开辟一个内存池,用于提高获取堆区内存的效率。而定位new就能将申请的内存通过调用构造函数来进行初始化。在后面学习的STL的链表中就能遇到这一场景。
在这里插入图片描述

7. malloc/free和new/delete的区别

1、malloc/free是库函数。new和delete是操作符。
2、对于内置类型来说malloc/free和new/delete的区别不是很大,对于自定义类型new/delete分别会去调用构造函数/析构函数。来进行初始化和清理工作。
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
4. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。

8.内存泄漏的概念

8.1.什么是内存泄漏

内存泄漏指的是在程序运行过程中,程序分配了一段内存空间,但在使用完这段内存空间后,没有及时释放掉这段内存,导致这段内存不能被再次使用,从而造成了内存空间的浪费。如果程序中存在内存泄漏问题,并且这种泄漏的情况不断累积,最终可能会导致程序所能使用的内存空间越来越小,甚至导致程序崩溃。内存泄漏是一种常见的编程错误,需要开发人员注意及时释放不再使用的内存空间,避免内存泄漏问题的出现。下面我通过一个样例来看看内存。

int main()
{
	char* p1 = (char*)malloc(1024 * 1024 * 1024);
	cout << p1 << endl;
	return 0;
}

在这里插入图片描述

8.2.内存泄漏的危害

对于客户端,内存泄漏的危害比较的小。对于服务端,内存泄漏的危害极大。因为想游戏服务、电商服务等服务端中,内存泄漏会导致服务程序宕机,进而导致软件业务的事故。所以我们程序员在操作动态内存时,一定要注意在哪里申请了就在对应的位置释放。内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

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

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

相关文章

JS的异或运算XOR

概念 异或&#xff08;xor&#xff09;是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”&#xff0c;计算机符号为“xor”。 两个值相同时&#xff0c;返回false&#xff0c;否则返回true。也就是说&#xff0c;XOR可以用来判断两个值是否不同。 JavaScript 语言…

企业级信息系统开发——Spring Boot加载自定义配置文件

文章目录 一、使用PropertySource加载自定义配置文件&#xff08;一&#xff09;创建Spring Boot Web项目ConfigDemo01&#xff08;二&#xff09;创建自定义配置文件&#xff08;三&#xff09;创建自定义配置类&#xff08;四&#xff09;编写测试方法&#xff08;五&#xf…

一键部署属于自己的ChatGPT-Next-Web

完整功能刚需&#xff1a; OpenAI 注册登录之后给的 api Key GitHub账号 Netlify账号 Tip&#xff1a; 注册 OepenAI账号 需要用国外手机号 这里建议去一些渠道购买账号 十块钱不到如果访问 OpenAI 的话 一定要挂欧美节点 否则禁止IP访问 概率会被封号为什么用 Netlify 托…

测试替身Test Doubles的5类型(Mockito)

测试替身Test Doubles的5类型(Mockito) 我们有一个名为 BankAccount 的类。 数据库用于存储和检索银行帐户信息。 我们想测试 BankAccount 中的逻辑&#xff0c;而不必担心它使用的底层数据库.由此类实现——它将 SQL 查询发送到数据库并返回其中包含的值。 测试替身Test Dou…

SuperMap iDesktopX扩展开发之GPA算子扩展

作者&#xff1a;dongyx SuperMap iDesktopX是超图研究院推出的一款跨平台的桌面GIS软件&#xff0c;兼容Windows和Linux&#xff0c;同时iDesktopX也采用的是插件式扩展开发框架&#xff0c;支持定制开发。 使用iDesktopX定制开发有以下优势&#xff1a; ⚫ 采用 Swing 图形界…

VM虚拟机仿真网络问题

在电子数据取证中&#xff0c;拿到一个镜像需要仿真的时候&#xff0c;经常会遇到网络问题。尤其是Linux服务器镜像&#xff0c;例如centos操作系统的镜像&#xff0c;一般镜像会有固定IP设置&#xff0c;仿真起来后&#xff0c;系统与本机不能建立连接&#xff0c;不能连接互联…

VSCode远程连接Ubuntu使用LLDB调试程序

VSCode已经具有远程开发的能力&#xff0c;可以使用SSH连接到Linux/MacOS进行远程开发&#xff0c;包括编译与调试&#xff0c;只需要安装Remote Development插件即可&#xff0c;如果想使用CMake管理项目&#xff0c;则需要将VSCode的CMake以及CMake Tools插件安装在远程机器上…

SpringBoot自定义打印横幅

众所周知&#xff0c;springboot项目启动的时候会打印横幅&#xff0c;横幅内容就是spring; 而spring boot提供了一个Banner接口用于处理启动横幅&#xff0c;默认情况下启动会打印如下信息 . ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \ ( (…

分布式系统

一.分布式理论基础 1.CAP理论 CAP定理是分布式系统中的重要理论&#xff0c;在一个分布式系统中最多只能同时满足一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和分区容错性&#xff08;Partition tolerance&#xff09;这三项中的…

以太网驱动的流程浅析(五)-mii_bus初始化以及phy id的获取

【硬件环境】 Imx6ul 【Linux kernel版本】 Linux4.1.15 【以太网phy】 Realtek8201f 1.1. 以太网驱动probe流程 1.1 mii_bus初始化以及phy id的获取 然后进行mii的一些初始化fec_enet_mii_init(pdev); 主要是对struct mii_bus这里的成员进行初始化 并且会做注册mdiobus的…

小笔记-简单但够用系列_jupyter notebook 的重新安装问题

文章目录 目的目标步骤 目的 做程序开发时&#xff0c;想到 jupyter notebook 的浏览器交互式执行&#xff0c;决定再次启用放置许久的 jupyter notebook。 但太久没有执行的 jupyter notebook 在打开页面有一旦打开或创建新的 python&#xff0c;就自动报错退出。 使用过往经…

Blender UV展开流程

目录 1. UV1.1 blender默认物体1.2 创建物体1.3 UV参考图1.4 标记缝合边1.5 UV拉伸1.6 孤岛模式 1. UV 1.1 blender默认物体 默认物体已经自动生成UV 在UV编辑工作区&#xff0c;编辑模式&#xff0c;全选物体在左边自动展开UV 在物体数据属性-UV贴图-存在默认的UV贴图&#…

华为OD机试真题B卷 Java 实现【输入整型数组和排序标识,对其元素按照升序或降序进行排序】,附详细解题思路

一、题目描述 输入整型数组和排序标识,对其元素按照升序或降序进行排序 数据范围: 1≤n≤1000 ,元素大小满足 0≤val≤100000 。 二、输入描述 第一行输入数组元素个数;第二行输入待排序的数组,每个数用空格隔开;第三行输入一个整数0或1。0代表升序排序,1代表降序排序…

UOS桌面系统使用RLinux恢复数据

UOS桌面系统使用RLinux恢复数据 一、工具介绍二、注意事项三、准备四、制作live系统启动盘五、拷贝文件六、进入live系统一、工具介绍 R-Linux 是一款用于 Linux 和某些 Unixes 操作系统 Ext2/Ext3/Ext4 FS 文件系统的免费文件恢复实用工具。R-Linux 与 R-Studio 使用相同的 I…

如何使用ArcGIS进行选房分析

无论是城市规划布局研究&#xff0c;还是为自己找一个心仪的住房&#xff0c;都需要综合考虑购物、医疗、教育和休闲等诸多因素&#xff0c;若单纯依靠人力去寻找&#xff0c;十分的麻烦和耗时。 此时ArcGIS强大的分析功能就凸显了出来&#xff0c;我们可以通过空间上的距离关…

chatgpt赋能python:Python中同一键可以对应多个值吗?

Python中同一键可以对应多个值吗&#xff1f; Python是一门简单、易学且功能强大的编程语言&#xff0c;它广泛应用于Web开发、机器学习、数据科学等领域。Python的数据结构中的字典&#xff08;dictionary&#xff09;是其中一个非常有用的数据结构&#xff0c;它可以存储键值…

解锁高并发世界:深入探索并发编程和线程池技术的实用指南

《深入理解高并发编程:JDK核心技术》这本书是一本非常实用的编程指南&#xff0c;旨在帮助读者深入理解并发编程和线程池技术。笔者将目录分为两大部分&#xff1a;基础篇、工具篇和线程池技术篇。 这本书提供了广泛的内容覆盖和深入的讲解&#xff0c;适合读者在高并发编程领…

MT8183核心板 MTK8183处理器规格参数

MT8183核心板集成了多项高性能硬件&#xff0c;是一款功耗低、高效能的芯片&#xff0c;可以支持高质量的平板电脑平台设计。该芯片结合了一个八核CPU&#xff0c;其中包括四个Arm Cortex-A73的“大核心”和四个Cortex-A53核心&#xff0c;全部运行速度高达2GHz&#xff0c;还有…

chatgpt赋能python:Python中的//2

Python中的//2 Python是一种广泛使用的动态编程语言&#xff0c;因为它功能强大&#xff0c;易于学习和使用。Python在每个程序员的工具包中占据重要位置&#xff0c;这是因为Python可以用于构建各种应用程序。 本文将讨论Python中的//2运算符&#xff0c;解释其作用和用法&a…

抖音seo源码开发-抖音搜索优化系统-视频批量剪辑系统搭建

抖音seo源码开发&#xff0c;抖音seo开源定制&#xff0c;抖音seo源码交付&#xff0c;抖音seo源码开发是一项重要的技术&#xff0c;可以将您的抖音号排名提升到更高的位置&#xff0c;帮助您吸引更多的关注和粉丝。SEO源码开发需要具备一定的技术和经验&#xff0c;因此建议在…