C++--内存管理

news2025/5/20 14:48:19

内存管理

1. C/C++内存分布

在C语言阶段,常说局部变量存储在栈区动态内存中的数据存储在堆区静态变量存储在静态区(数据段)常量存储在常量区(代码段),其实这里所说的栈区、堆区、静态区以及常量区都是 虚拟进程地址空间 的一部分,其中具体内存区域的划分如下:

在这里插入图片描述

【说明】:

  • :又叫堆栈,用于存储非静态局部变量、函数参数以及函数返回值等等;栈是向下增长的。
  • :用于程序运行时进行动态内存分配;堆是向上增长的。
  • 数据段 (静态区):Linux 中通常叫作数据段,用于存储存储全局数据和静态数据。
  • 代码段 (常量区):Linux 中通常叫作代码段,用于存储可执行的代码指令和只读常量。
  • 内存映射段:是一种高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,实现进程间通信;
  • 内核空间:操作系统内核 – kernel,受硬件保护,用户不能进行读写,用于执行各种机器指令。

注意:虚拟进程地址空间、多进程多线程以及操作系统内核等相关知识会在 Linux 中进行学习,这里只需要了解 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 在哪里?____
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____

2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____;    strlen(char2) = ____;
sizeof(pChar3) = ____;   strlen(pChar3) = ____;
sizeof(ptr1) = ____;

3. sizeof 和 strlen 区别?
    
答案:(横向)
1、选择题:C C C A A A A A D A B
2、填空题:40 5 4 4/8 4 4/8
3sizeof是操作符/关键字,后面可以不加括号,而直接跟类型,另外,sizeof计算的是变量所占空间的字节数;而strlen是函数,调用必须加括号,且strlen计算的是字符串中字符的个数(不包括'\0')

易错点:

  • sizeof(数组名) 的含义是计算整个数组的大小。
  • *char2*char2 的存储区域:
    • 对于 char2 中的内容是一个常量字符串 abcd 不可以修改存在放在常量区,但是此处的 char2 是一个数组,数组在栈区上开辟空间,本质 char2 中的数据是从常量区中拷贝而来的,所以 char2栈区
    • 对于 *char2 其实本质是对一个数组名进行解引用,这里的数组名的含义是数组首元素的地址,对数组首元素地址进行解引用之后得到的就是数组中的首元素,但是因为数组首元素就是在栈区,所以 *char2 也在栈区
  • pchar3*pchar3 的存储区域:
    • 对于 pchar3 是一个地址指向常量区的 abcd ,但是针对 pchar3 本身来说它就是一个局部变量,所以 pchar3栈区
    • 对于 *pchar3 是对 pchsr3 进行解引用,得到的就是 abcd ,但是 abcd常量区,所以 *pchar3 就在**常量区(代码段)**上

在这里插入图片描述

2. C/C++内存管理方式

2.1 C语言内存管理方式

在C语言中使用 malloc/calloc/realloc/free 函数来进行动态内存管理:

void Test()
{
	int* p1 = (int*)malloc(sizeof(int));
	if (p1 == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int* p2 = (int*)calloc(4, sizeof(int));
	if (p2 == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}

	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
	if (p3 == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}

	free(p1);
    free(p2);
	free(p3);
}

补充:malloc/calloc/realloc的区别?

  • malloc 用于开辟一块动态内存,使用时需要指定开辟的空间大小 (字节),如果开辟成功返回空间的起始地址,如果开辟失败返回 NULL,且不会初始化。
  • calloc 的用法和 malloc 类似,只是它有两个参数,第一个参数为元素个数,第二个参数为每个元素的大小,并且它会将该空间中的数据全部初始化为0。
  • realloc 用于空间的扩容/缩容,它有两个参数,第一个参数为需要调整的动态内存的起始地址,第二个参数为调整后的空间大小,如果第一个参数为 NULL,则它等价于 malloc;如果扩容,编译器会检查原空间后是否有足够的空间,如果足够,就直接扩容并返回原空间的起始地址,如果不够,就新开辟一块空间,然后将原空间的数据拷贝到新空间并返回新空间的地址,最后再释放原空间;如果缩容,编译器会直接新开辟一块空间,然后拷贝原空间数据到新空间并返回新空间的地址,再释放原空间。

2.2 C++动态内存管理方式

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

2.2.1 new/delete 操作内置类型

对于内置类型,C语言和C++内存管理方式没有明显区别,只是C++中使用 new 操作符来替代C语言中的 malloc/calloc 函数,使用 delete 操作符来替代 free 函数 ;

同时,由于 new 和 delete 是操作符/关键字,而不是函数,所以它们后面不需要跟括号,而是直接跟类型即可。另外,new 可以在开辟空间的同时进行初始化

注意:C++不支持扩容,要扩容都是自己开辟新空间、拷贝数据,然后再销毁原空间。

void Test()
{
	//申请单个空间不初始化
	int* p1 = new int;

	//申请单个空间并初始化
	int* p2 = new int(10);

	//申请连续空间不初始化
	int* p3 = new int[10];

	//申请连续空间并初始化(为初始化的部分会自动由编译器自动初始化)
	int* p4 = new int[10]{ 1,2,3,4,5 };

	//释放单个空间
	delete p1;
	delete p2;

	//释放多个空间
	delete[] p3;
	delete[] p4;
}

在这里插入图片描述

在这里插入图片描述

注意:申请和释放单个元素的空间,使用 new 和 delete 操作符,申请和释放连续的空间,使用 new[] 和 delete[],注意二者一定要匹配使用,即不能用 delete 来释放 new[] 开辟的空间。

2.2.2 new/delete 操作自定义类型

C++动态内存管理和C语言动态内存管理最大的不同在于二者对自定义类型的处理:C语言 malloc/calloc/realloc 函数只负责开辟空间,free 函数只负责销毁空间。而C++在申请自定义类型的空间时,new 会调用构造函数,delete 会调用析构函数,完成对应对象的初始化和销毁。

class A
{
public:
    //构造函数
	A(int a = 0)
		: _a(a)
	{
		cout << "A 构造" << this << endl;
	}
    
    //析构函数
	~A()
	{
		cout << "A 析构" << this << endl;
	}
private:
	int _a;
};

在这里插入图片描述

**注意:**先调用析构函数再释放空间。

3. operator new 与 operator delete

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

注意: operator newoperator delete 函数不是运算符重载,因为它们的参数没有自定义类型,而是库里面实现的全局函数,仅仅是将它们取名为 operator 而已

C++底层的 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);  //此处调用free函数
	__FINALLY
		_munlock(_HEAP_LOCK);  // release other threads
	__END_TRY_FINALLY
		return;
}

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

new 和 delete

在实际的编译器中,通过查看反汇编来验证 newdelete 的底层调用。下面以开辟内置类型的空间为示例:

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

在这里插入图片描述

new[] 和 delete[]

而对于new[]delete[] 来说,它们通过调用 operator new[]operator delete[] 函数来实现其功能,但是其实 operator new[]operator delete[] 底层也是调用的 operator newoperator delete 函数:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

补充:

通过上述的代码知道 operator new 实际也是通过 malloc 来申请空间,那么为什么还需要对 malloc 进行封装成新的 operator new 函数呢?

因为如果 malloc 申请空间成功就返回新空间的首地址,在申请失败的时候就会返回一个空指针。因为C++是一门面向对象的语言,这样的返回值是无法提现出这样的特性。

所以为了完善这里C++提供了一个对 malloc 新封装的函数 operator new 如果内存正常申请即和 malloc 一样,但是如果申请内存失败就会执行用户提供的空间不足的应对措施,如果用户提供措施就继续申请,否则抛出一个 bad_alloc 类型异常,可以在 main 函数中利用 catch 进行捕获,这样的设计就更加符合C++是一门面向对象语言的特点。

同时因为 operator newoperator delete 还是内置的去全局函数,也可以在程序中调用。

A* p1 = (A*)operator new (sizeof(A));
operator delete (p1);

同样可以起到开辟空间,销毁空间的目的,但是这样使用就不会调用构造函数和析构函数进行初始化和资源的销毁。但是这样使用又比直接使用 malloc 更加方便,因为其不需要再对新申请的空间进行检查,因为其失败了会抛异常。需要在外层捕获异常。

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

在这里插入图片描述

在这里插入图片描述

可以看到,对于自定义类型,C++ 中的 new 和 delete 不仅仅会进行空间的申请和释放,还会进行构造和析构,这也正是为什么在C语言已经有了malloc/calloc/realloc/free 的情况下,C++ 又单独创造了 new 和 delete 的原因。

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

定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

其使用格式如下:

new(place_address) type  
new (place_address) type(initializer-list)

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

示例如下:

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

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

private:
	int _a;
};

int main()
{
    //p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	if (p1 == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//定位new--对p1指向的空间显式调用构造函数
	new(p1)A(1);

	p1->~A();  //析构函数可以直接显式调用,或者直接使用delete
	free(pa);
}

在这里插入图片描述

注意:构造函数在平时的代码中不可以被显示调用,这中使用定位new的方法可以显示调用构造函数。析构函数可以被显示调用,只需要指定好对应对象即可。

这种使用场景其实在平时的开发过程中基本不会使用,因为如上的 main 函数代码就是两行 newdelete 就可以解决的事情。所以**定位 new **的使用场景基本在内存池中。

使用场景:

定位 new 表达式在实际中一般是配合内存池使用

因为内存池分配出的内存对象是没有被初始化的,所以如果是自定义类型的对象,需要使用 new 的定义表达式进行显示调构造函数进行初始化。而内存池在后面会详细学习,此处了解一下即可。

简单理解内存池:

假设半山腰有一个村子,但由于各种原因村子中没有水喝,所以人们每次喝水都只能到山下的公共水井处排队打水,但是呢排队很慢,所以村长就用抽水机+水管联通水井在自己家建了一个蓄水池,以后要用水就直接到蓄水池中去取即可,而不用再到山下去排队打水了,大大提高了效率。

上述例子中全村公用的水井就相当于堆,其他村民排队打水就相当于 malloc/calloc/realloc 函数向堆区申请空间,而村长家的蓄水池就相当于内存池,其实本质也就相当于一次性申请了很大一部分内存供自己的对象本身进行使用,减少了频繁对堆的空间内存申请,内存池的建立可以使得申请空间的效率变得很高。

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

6.1 共同点

malloc/freenew/delete 的共同点是:*都是从堆上申请空间,并且需要用户手动释放

6.2 不同点

  • mallocfree 是函数,newdelete 是操作符。
  • 申请内置类型空间时,malloc 申请的空间不可以初始化,new 可以初始化。
  • 申请自定义类型对象时,malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理。
  • malloc 申请空间时,需要手动计算空间大小并传递,new 只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即。
  • malloc 的返回值为 void* , 在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型。
  • malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空,new 不需要,但是 new 需要捕获异常。

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

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

相关文章

TC3xx学习笔记-UCB BMHD使用详解(二)

文章目录 前言Confirmation的定义Dual UCB: Confirmation StatesDual UCB: Errored State or ECC Error in the UCB Confirmation CodesECC Error in the UCB ContentDual Password UCB ORIG and COPY Re-programming UCB_BMHDx_ORIG and UCB_BMHDx_COPY (x 0-3)BMHD Protecti…

用Python实现数据库数据自动化导出PDF报告:从MySQL到个性化文档的全流程实践

本文将介绍如何使用Python构建一个自动化工具&#xff0c;实现从MySQL数据库提取员工数据&#xff0c;并为每位员工生成包含定制化表格的PDF报告。通过该方案&#xff0c;可显著提升数据导出效率&#xff0c;避免手动操作误差&#xff0c;同时支持灵活的格式定制。 需求&#…

实战设计模式之状态模式

概述 作为一种行为设计模式&#xff0c;状态模式允许对象在其内部状态改变时&#xff0c;改变其行为。这种模式通过将状态逻辑从对象中分离出来&#xff0c;并封装到独立的状态类中来实现。每个状态类代表一种特定的状态&#xff0c;拥有自己的一套行为方法。当对象的状态发生变…

什么是着色器 Shader

本人就是图形学结课了&#xff0c;对 OpenGL着色器还有很多疑问嘿嘿 文章目录 为什么要有着色器vshaderfshader 本文围绕 vshader 和 fshader 代码示例讲解。 &#xff08;着色器代码取自本人简单OpenGL项目 https://github.com/DBWGLX/-OpenGL-3D-Lighting-and-Shadow-Modeli…

Redis的主从架构

主从模式 全量同步 首先主从同步过程第一步 会先比较replication id 判断是否是第一次同步假设为第一次同步 那么就会 启动bgsave异步生成RDB 同时fork子进程记录生成期间的新数据发送RDB给从节点 清空本地数据写入RDB 增量同步 对比ReplicationID不同因此选择增量同步在Rep…

博客系统功能测试

博客系统网址&#xff1a;http://8.137.19.140:9090/blog_list.html 主要测试内容 功能测试、界面测试、性能测试、易用性测试、安全测试、兼容性测试、弱网测试、安装卸载测试、压力测试… 测试方法及目的 利用selenium和python编写测试脚本&#xff0c;对博客系统进行的相关…

【深度学习新浪潮】什么是多模态大模型?

多模态大模型是人工智能领域的前沿技术方向,它融合了多种数据模态(如文本、图像、语音、视频、传感器数据等),并通过大规模参数模型实现跨模态的联合理解与生成。简单来说,这类模型就像人类一样,能同时“看”“听”“读”“说”,并将不同信息关联起来,完成复杂任务。 …

机器学习前言2

1.机器学习 2.机器学习模型 3.模型评价方法 4.如何选择合适的模型 介绍 机器学习&#xff08;Machine Learning, ML&#xff09;是人工智能&#xff08;AI&#xff09;的核心分支&#xff0c;致力于通过数据和算法让计算机系统自动“学习”并改进性能&#xff0c;而无需显式编…

【成品设计】基于Arduino的自动化农业灌溉系统

《基于STM32的单相瞬时值反馈逆变器》 硬件设计&#xff1a; ESP-C3最小系统板&#xff1a;主控芯片&#xff0c;内部集成wifi。土壤湿度传感器&#xff1a;采集土壤湿度。温度传感器&#xff1a;采集土壤温度。水泵模块&#xff1a;水泵继电器软管。按键3个&#xff1a;参数…

前端页面 JavaScript数据交互

前言&#xff1a;学习JavaScript为前端设置动态效果&#xff0c;实现交互。JavaScript是一种广泛应用于网页开发的脚本语言&#xff0c;它能够使网页变得更加动态和交互性。作为一种客户端脚本语言&#xff0c;JavaScript可以被嵌入到HTML中&#xff0c;并且可以被所有现代的网…

esp32课设记录(三)mqtt通信记录 附mqtt介绍

目录 安装mqttx&#xff08;云端部署&#xff09; 安装mosquitto&#xff08;本地部署&#xff09; 编程&#xff0c;连接wifi 编程&#xff0c;连接mqtt&#xff0c;实现数据接收 实际效果展示&#xff1a; 附录&#xff1a;mqtt介绍 工作流程简述&#xff1a; 工作流…

string类(详解)

【本节目标】 1. 为什么要学习string类 2. 标准库中的string类 3. string类的模拟实现 4. 扩展阅读 1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 C 语言中&#xff0c;字符串是以 \0 结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c; C 标准库中提供…

MATLAB | R2025a 更新了哪些有趣的东西?

千呼万唤始出来&#xff0c;MATLAB R2025A 来见面&#xff0c;这次更新比往常晚了两个月&#xff0c;让我们看看更了哪些好玩的新东西叭&#xff1a;首先下载更新启动一气呵成&#xff0c;映入眼帘的是&#xff1a; 1 基本界面 基本界面变得和 MATLAB 网页版一模一样了&#…

前缀和——和为K的子数组

作者感觉本题稍稍有点难度&#xff0c;看了题解也思考了有一会TWT 显然&#xff0c;暴力我们是不可取的&#xff0c;但这里我们可以采取一种新的遍历数组形式&#xff0c;从后向前&#xff0c;也就是以i位置为结尾的所有子数组&#xff0c;这个子数组只统计i位置之前的。 然后…

深入理解 ZAB:ZooKeeper 原子广播协议的工作原理

目录 ZAB 协议&#xff1a;ZooKeeper 如何做到高可用和强一致&#xff1f;&#x1f512;ZAB 协议的核心目标 &#x1f3af;ZAB 协议的关键概念 &#x1f4a1;ZAB 协议的运行阶段 &#x1f3ac;阶段一&#xff1a;Leader 选举 (Leader Election) &#x1f5f3;️阶段二&#xff…

GraphPad Prism项目的管理

《2025新书现货 GraphPad Prism图表可视化与统计数据分析&#xff08;视频教学版&#xff09;雍杨 康巧昆 清华大学出版社教材书籍 9787302686460 GraphPadPrism图表可视化 无规格》【摘要 书评 试读】- 京东图书 GraphPad Prism统计数据分析_夏天又到了的博客-CSDN博客 项目…

驱动-Linux定时-timer_list

了解内核定时相关基础知识 文章目录 简要介绍timer_list 特点API 函数实验测试程序 - timer_mod.c编译文件-Makefile实验验证 注意事项总结 简要介绍 硬件为内核提供了一个系统定时器来计算流逝的时间&#xff08;即基于未来时间点的计时方式&#xff0c; 以当前时刻为计时开始…

STM32F103_LL库+寄存器学习笔记22 - 基础定时器TIM实现1ms周期回调

导言 如上所示&#xff0c;STM32F103有两个基本定时器TIM6与TIM7&#xff0c;所谓「基本定时器」&#xff0c;即功能最简单的定时器。 项目地址&#xff1a; github: LL库: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library22_Basic_Timer寄存器方…

5个yyds的.Net商城开源项目

今天一起来盘点下5个商城开源项目。 1、支持多语言、多商店的商城&#xff0c;.Net7 EF7领域驱动设计架构&#xff08;Smartstore&#xff09; 项目简介 Smartstore 支持桌面和移动平台、多语言、多商店、多货币的商城&#xff0c;并支持SEO优化&#xff0c;支持无限数量的…

[项目深挖]仿muduo库的并发服务器的解析与优化方案

标题&#xff1a;[项目深挖]仿muduo库的并发服务器的优化方案 水墨不写bug 文章目录 一、buffer 模块&#xff08;1&#xff09;线性缓冲区直接扩容---->环形缓冲区定时扩容&#xff08;只会扩容一次&#xff09;&#xff08;2&#xff09;使用双缓冲&#xff08;Double Buf…