c++阶梯之内存管理

news2025/2/23 11:50:54

目录

1.c/c++内存分布

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

3.c++的内存管理方式 

3.1 new/delete操作内置类型

3.2 new/delete操作自定义类型 

3.3 实践中总结new与malloc的区别 

1. new的用法比malloc更加的简单

2. new可以控制初始化,malloc不可以

3. 对于自定义类型,new/delete除了开空间还会调用构造函数和析构函数

4.  operator new与operator delete

4.1 operator new与operator delete函数(重点) 

5. new和delete的实现原理 

5.1内置类型

 5.2 自定义类型

6. 常见面试题

6.1 malloc/free与new/delete的区别

6.2 内存泄漏 

1. 什么是内存泄漏 ?内存泄露的危害?

6.3 内存泄露的分类

6.4 如何避免内存泄漏?


 

1.c/c++内存分布

下图是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.代码段(常量区)

(1)globalVar在哪里?__C__
(3)staticVar在哪里?___C_
(5)num1 在哪里?__A__
(2)staticGlobalVar在哪里?__C__
(4)localVar在哪里?__A__
在这段代码里,1是全局变量,因此必然在静态区(c语言里叫做静态区,但c++里叫做数据段),2、3都是static修饰的静态变量,因此也在静态区;4、5是定义在Test函数内的局部变量,区别在于4是整形变量,5是num1[]的数组名,都存在于栈区。
(1)char2在哪里?__A__
(3)pChar3在哪里?__A__
(5)ptr1在哪里?__A__
(2)*char2在哪里?__A_
(4)*pChar3在哪里?__D__
(6)*ptr1在哪里?__B__

< > 1这一行的含义是,定义一个字符数组char2[],将常量区的字符串"abcd\0"拷贝到这个字符数组中,因此无论是1还是2,都在栈上。

< > 3这一行的含义是定义一个不可更改指向的指针pChar3,并让他指向常量区的字符串"abcd\0",因此pChar3是栈上的一个指针变量,指向常量区字符串"abcd\0"的首地址;而*pchar(4)则是常量区字符串的首元素'a'。这里要记住,并不是用const修饰的变量就在静态区。

< > 5这一行的含义是在堆上开辟一个16字节的整型数组,注意:动态开辟是在堆上开辟空间,但指向这个空间的指针还是在堆上的。因此ptr1是栈上的指针变量,而*ptr1就是在堆上开辟的空间首地址的解引用。

2. 填空题:
(1)sizeof(num1) = __40__;(包含十个元素的整型数组)

(2)sizeof(char2) = __5__;(包含五个字符的数组大小,含'\0')

(3)strlen(char2) = __4__;(字符串内字符个数<不包括'\0'>)
(4)sizeof(pChar3) = __4/8__;(指针)

(5)strlen(pChar3) = __4__;(字符串内字符个数<不包括'\0'>)
(6)sizeof(ptr1) = __4/8__;(指针)
3. sizeof 和 strlen 区别(大家可以自己测一测)
 1、sizeof是运算符,strlen是C语言标准库函数。
2、 strlen 测量的是字符串的实际长度,以’\0’ 结束,返回结果不包括’\0’ 。
3、而sizeof 测量的是字符的分配大小,它的参数可以是数组、指针、类型、对象、函数等。

如下图详解

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

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

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的区别是什么?

malloc()函数用于在堆内存中开辟一块空间,成功则返回一个指向该空间的指针,失败返回NULL.

calloc()函数用于在堆内存中开辟一块空间,并将其中的每一个元素初始化为0,成功则返回一个指向该空间的指针,失败返回NULL。

reaclloc函数用于给一个已经分配了地址的指针重新分配空间(默认用于扩容),分为原地扩容成功与异地扩容,成功则返回一个指向该空间的指针,失败返回NULL。

2.这里需要free(p2)吗?

不需要。realloc重新分配的时候,会释放原有空间,如果此时我们再释放原有空间,就会多次释放同一空间,导致代码运行出错。

3.c++的内存管理方式 

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

3.1 new/delete操作内置类型

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);
	// 动态申请3个int类型的空间
	int* ptr3 = new int[3];
    //动态申请3个int类型的空间并初始化为1,2,3
	int* ptr4 = new int[3]{ 1,2,3 };
	for (int i = 0; i < 3; i++)
	{
		cout << ptr3[i] << " ";
	}
	cout << endl;
	for (int i=0;i<3;i++)
	{
		cout << ptr4[i] << " ";
	}
	delete ptr1;
	delete ptr2;
	delete[] ptr3;
	delete[] ptr4;
}

我们来看看上述代码的结果与解析图示。

 

 

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

3.2 new/delete操作自定义类型 

new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数

有这么一个类,我们分别用malloc与new来开辟该类型的空间。

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

malloc:

 

new:

 

3.3 实践中总结new与malloc的区别 

1. new的用法比malloc更加的简单

// 动态申请一个int类型的空间
int* ptr0 = (int*)malloc(sizeof(int));
int* ptr1 = new int;

//动态申请连续int类型的空间
int* ptr0 = (int*)malloc(sizeof(int)*10);
int* ptr1 = new int[10];

2. new可以控制初始化,malloc不可以

3. 对于自定义类型,new/delete除了开空间还会调用构造函数和析构函数

接下来我们分别用 malloc与new创建一个单链表,做一个对比。

malloc

typedef struct ListNode
{
	ListNode* _next;
	int _val;
}ListNode;

ListNode* CreatListNode(int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->_next = NULL;
	newnode->_val = x;
	return newnode;
}

ListNode* CreateList(int n)
{
	ListNode head;
	head._next = NULL;
	head._val = -1;
	ListNode* tail = &head;
	int val = 0;
	printf("请依次输入%d个值", n);
	for (int i = 0; i < n; i++)
	{
		cin >> val;
		tail->_next = CreatListNode(val);
		tail = tail->_next;
	}
	return head._next;
}

int main()
{
	ListNode* Node=CreateList(5);
	return 0;
}

new 


typedef struct ListNode
{
public:
	ListNode(int x=0)
		:_next(nullptr)
		,_val(x)
	{}
	ListNode* _next;
	int _val;
}ListNode;

ListNode* CreateList(int n)
{
	ListNode head(-1);
	ListNode* tail = &head;
	int val = 0;
	printf("请依次输入%d个值", n);
	for (int i = 0; i < n; i++)
	{
		cin >> val;
		tail->_next = new ListNode(val);
		tail = tail->_next;
	}
	return head._next;
}
int main()
{
	ListNode* List = CreateList(5);
	return 0;
}

有没有发现,我们并没有对new开辟的空间进行检查,并且malloc的实现比new多出了一个创建节点的函数。对这两个问题,我们将一一进行解答。

我们为什么没有对new开辟的空间进行检查呢?

这是因为new在申请空间失败时会抛异常,malloc会返回NULL。(这一点会在后面new原理一节详解)。

new为什么没有写创建节点的函数?

 看看我们的标题,new开空间会调用构造函数,而delete释放空间会调用析构函数。因此我们使用new开辟节点时,自动调用构造函数创建了节点。

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

 

4.  operator new与operator delete

 这里要注意了,operator new与operator delete并不是new与delete的重载函数。new和delete是操作符。

4.1 operator new与operator delete函数(重点)
 

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
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);
	__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. new和delete的实现原理 

5.1内置类型

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

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

 看下面这段代码,A类中只有一个int成员,那么main函数中new出来的两个空间应该大小一致对吗?

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

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

int main()
{
	A* p = new A[10];

	int* p1 = new int[10];
	delete[] p;
	delete[] p1;
	return 0;
}

 很显然,答案是不对的,那么这是为什么呢?我们往下看。

当我们屏蔽掉析构函数后,我们发现结果改变了。

这是因为,delete时需要调用析构函数,那么delete怎么知道要调用多少次析构函数呢?

因此当类中有显式生成的析构函数时,new出的类类型空间开头就会开辟一定的空间用来存放需要调用析构函数的次数。如果程序员没有显式定义,编译器会做优化(不用调用析构函数,因此不需要开辟空间存放) 

6. 常见面试题

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

6.2 内存泄漏 

1. 什么是内存泄漏 ?内存泄露的危害?

 什么是内存泄漏?

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害?

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

 譬如:

void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}

6.3 内存泄露的分类
 

C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定 

6.4 如何避免内存泄漏?

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。 

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

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

相关文章

C# OpenVINO Nail Seg 指甲分割 指甲检测

目录 效果 模型信息 项目 代码 数据集 下载 C# OpenVINO Nail Seg 指甲分割 指甲检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-02-29T16:41:28.273760 author&#xff1a;Ultralytics task&#xff1a;segment version&#…

遥感、航拍、影像等用于深度学习的数据集集合

遥感图像的纹理特征异常繁杂&#xff0c;地貌类型多变&#xff0c;人工提取往往存在特征提取困难和特征提取不准确的问题&#xff0c;同时&#xff0c;在这个过程中还会耗费海量的人力物力。随着计算力的突破、数据洪流的暴发和算法的不断创新&#xff0c;在具有鲜明“大数据”…

Python爬虫——Urllib库-上

这几天都在为了蓝桥杯做准备&#xff0c;一直在刷算法题&#xff0c;确实刷算法题的过程是及其的枯燥且枯燥的。于是我还是决定给自己找点成就感出来&#xff0c;那么Python的爬虫就这样开始学习了。 注&#xff1a;文章源于观看尚硅谷爬虫视频后笔记 目录 Urllib库 基本使…

Html零基础入门教程(非常详细)

文章目录 1.认识HTML2.html 框架3.HTML常见标签4.HTML语法特征5.列表 1.认识HTML html是超文本标记语言: 目前最新版本是html5,由w3c(万维网联盟)完成标准制定。 声明文档的类型是html5 超文本标记语言。 HTML &#xff0c;全称“Hyper Text Markup Language&#xff08;超文…

微信小程序图片展示淡入淡出纯WXSS实现,无需使用消耗性能的动画引擎

进入下面小程序可以体验效果&#xff1a; 以下代码的淡入淡出是切换图片的时候动画效果显示的。需要用其他方式&#xff0c;可以基于这个wxss修改即可 原理就是&#xff0c;图片默认样式的opacity 是 0&#xff0c;通过变量改变样式的opacity即可&#xff0c;然后需要有transi…

Vue纯前端实现链接生成二维码并支持下载

你好&#xff0c;我是小白Coding日志&#xff0c;一个热爱技术的程序员。在这里&#xff0c;我分享自己在编程和技术世界中的学习心得和体会。希望我的文章能够给你带来一些灵感和帮助。欢迎来到我的博客&#xff0c;一起在技术的世界里探索前行吧&#xff01; 在现代 Web 应用…

(C语言)Sleep函数,system函数,数组练习,详解与运用

一维数组详解&#xff1a;http://t.csdnimg.cn/zahZF 二维数组详解&#xff1a;http://t.csdnimg.cn/h2mLe 我们看过可一维数组与二维数组&#xff0c;现在我们来进行简单的练习。 题目&#xff1a;编写代码&#xff0c;演⽰多个字符从两端移动&#xff0c;向中间汇聚 1. …

DFS之剪枝与优化

剪枝 1.优化搜索顺序&#xff1a;在大部分情况下&#xff0c;我们应该优先搜索分支较少的结点 2.排除等效冗余&#xff08;在不考虑顺序的情况下&#xff0c;尽量用组合的方式来搜索&#xff09; 3.可行性剪枝 4.最优性剪枝 5.记忆化搜索 165. 小猫爬山 - AcWing题库 import …

【快速上手QT】06-检测按键检测鼠标

今天聊聊事件&#xff0c;实际上我们在前两篇文章中就已经接触到了事件&#xff0c;分别是定时器事件和绘画事件&#xff0c;今天我们再来看看其他的事件。 我们打开QT助手&#xff0c;在QWidget的界面中找到下图中的地方。 我们可以看到很多函数的结尾都是Event&#xff0c;那…

日本极致产品力|200人的小型家族企业,如何年销7亿块巧克力?

蒂罗尔巧克力是日本经典的巧克力品牌。糖果业务是其早期的主营业务&#xff0c;在主营业务下滑的情况下&#xff0c;确立新的竞争方向、打造新产品、寻找新方法&#xff0c;成就巧克力极致产品力重回增长。 竞争环境变化&#xff1a;糖果主营业务持续下滑 始于1903年的松尾株式…

云原生架构技术揭秘:探索容器技术的奥秘

云原生的概念和演进都是围绕云计算的核心价值展开的&#xff0c;比如弹性、自动化、韧性&#xff0c;所以云原生所涵盖的技术领域非常丰富。 随着云计算技术的不断发展&#xff0c;云原生架构已经成为了新一代软件开发的重要趋势。本文将为您介绍云原生架构的相关技术&#xf…

产品经理岗位的任职资格和职业规划

产品经理主要是商业银行以客户为导向的&#xff0c;具体负责组织银行某一金融产品线的创新设计、生产营销和管理服务的工作。这类人士主要负责应用实施工作&#xff0c;其中产品线由一系列的产品构成&#xff0c;公司的产品经理主要分为全过程产品创新设计专家、全过程产品生产…

Decision Transformer

DT个人理解 emmm, 这里的Transformer 就和最近接触到的whisper一样,比起传统Transformer,自己还设计了针对特殊情况的tokens。比如whisper里对SOT,起始时间,语言种类等都指定了特殊tokens去做Decoder的输入和输出。 DT这里的作为输入的Tokens由RL里喜闻乐见的历史数据:…

QtCreator报Failed to parse qmlimportscanner output解决

错误如下: 定位错误位置 增加错误信息打印 打印执行命令 执行打印输出的命令,成功返回JSON 但输出的JSON对象不是json格式,而是命令 增加$$成功输出JSON 使用QtCreator12编译一次后,再使用QtCreator13成功编译通过,问题解决

Floyd算法、Dijkstra算法、基础拓扑排序

Floyd算法 Dijkstra算法 基础拓扑排序

TikTok运营应该使用什么IP?网络问题大全

想要迈过TikTok新手门槛&#xff0c;首先必须要学习的就是网络问题。很多人开始做TikTok账号或者TikTok小店时&#xff0c;都会遇到一些先前没有遇到的词汇和概念&#xff0c;比如原生IP&#xff0c;独享IP&#xff0c;甚至专线&#xff0c;那么一个IP可以做几个账号呢&#xf…

编译 qsqlmysql.dll QMYSQL driver not loaded

Qt 连接MySQL数据库&#xff0c;没有匹配的qsqlmysql.dll, 需要我们跟进自己Mysql 以及QT版本自行编译的。异常如下图&#xff1a; 安装环境为 VS2019 Qt5.12.12&#xff08;msvc2017_64、以及源码&#xff09; 我的安装地址&#xff1a;D:\Qt\Qt5.12.12 Mysql 8.1.0 默认安…

【C++从0到王者】第四十五站:图

文章目录 一、图的概念1.图概念2.顶点与边的概念3.有向图和无向图4.完全图5.邻接顶点6.顶点的度7.路径与路径长度8.简单路径与回路9.子图10.连通图与强连通图11.生成树 二、图的存储结构1.邻接矩阵1.1 基本概念1.2 代码实现 2.邻接表1.1 基本概念1.2 代码实现 3.总结 一、图的概…

如何设计一个秒杀系统?

秒杀是电商系统中常见的业务&#xff0c;用于吸引用户&#xff0c;刺激留存及消费所做的一种活动。经典的秒杀包含限时秒杀和限量秒杀。很多公司有专门的秒杀系统。哪个业务要做活动&#xff0c;就来对接这个系统。 系统特点 1、瞬时流量极大&#xff0c;过了秒杀时间点流量结束…

【HTML】HTML基础2(一些常用标签)

目录 例子 首先是网页图标 然后是一些常用标签 插入图片 例子 <!DOCTYPE html> <html><head><link rel"icon" href"img/银河护卫队-星爵.png" type"image/x-icon"><meta charset"utf-8"><title>…