C++第七弹 -- C/C++内存管理

news2024/9/20 8:11:59

目录

  • 前言
  • 一. C/C++内存分布
  • 二. C语言中动态内存管理方式
  • 三. C++中动态内存管理
  • 四. operator new与operator delete函数
  • 五. new和delete的实现原理
    • 1.内置类型
    • 2. 自定义类型
  • 六. 定位new表达式(placement-new)
  • 七. 常见面试题
  • 总结

前言

在C/C++编程中,内存管理是至关重要的一个环节。程序员需要合理地分配和释放内存,以确保程序能够正常运行,避免内存泄漏和崩溃。本文将深入探讨C/C++内存管理机制,从内存分布、动态内存管理方式、new和delete的实现原理到定位new表达式,以及malloc/free和new/delete的区别,全面解析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.代码段(常量区)
   globalVar在哪里?____ C  staticGlobalVar在哪里?____C
   staticVar在哪里?____C   localVar在哪里?____A
   num1 在哪里?____A
   
   char2在哪里?____A   *char2在哪里?___A
   pChar3在哪里?____A      *pChar3在哪里?____D
   ptr1在哪里?____ A       *ptr1在哪里?____B
2. 填空题:
   sizeof(num1) = ____;40
   sizeof(char2) = ____;5      strlen(char2) = ____;4
   sizeof(pChar3) = ____;4     strlen(pChar3) = ____;4
   sizeof(ptr1) = ____;4
3. sizeof 和 strlen 区别?




char2会在栈中存储, 而*char2内字符串在常量区, 但是因为是使用数组,所以会将abcd\0从常量区拷贝一份到栈区的数组

在这里插入图片描述
sizeof 和 strlen 区别?

  1. 定义和类型
    sizeof:是一种运算符,用于计算对象或类型所占的空间大小(以字节为单位)。它在编译时就已经确定了值,并将结果在编译时嵌入到程序中。在头文件中,sizeof 通常被 typedef 为 unsigned int。
    strlen:是一种函数,用于计算字符串的长度,即字符串中字符的数量(不包括结束符 \0)。它定义在 <string.h> 头文件中,其原型为 size_t strlen(const char* str);。strlen 在运行时才能计算字符串的长度。
  2. 用法
    sizeof:
    可以用于基本数据类型(如 int、float 等)。
    可以用于数组、指针、结构体、联合等复合数据类型。
    可以直接作用于变量名或类型名,而不需要变量名或类型名被括号括起来(但使用括号可以提高代码的可读性)。
    对于指针,sizeof 返回的是指针本身的大小,而不是指针所指向的内存区域的大小。
    strlen:
    只能用于字符型指针(char*),且该指针必须指向以 \0 结尾的字符串。
    strlen 的参数必须是字符串的起始地址。
  3. 功能和结果
    sizeof:
    返回的是对象或类型所占的空间大小,单位是字节。
    对于数组,sizeof 返回的是整个数组所占的空间大小,包括所有元素和末尾的 \0(如果数组是字符数组的话)。
    对于指针,sizeof 返回的是指针本身的大小,与指针所指向的数据类型无关。
    strlen:
    返回的是字符串中字符的数量,不包括结束符 \0。
    如果传入的指针不是以 \0 结尾的,strlen 会继续读取内存直到遇到 \0 为止,这可能会导致越界访问。

在这里插入图片描述

【说明】

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

二. 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 );
//不需要,如果realloc成功, 会在p2的基础上进行扩容, 返回的是p2
//如果扩容失败,realloc会自动释放p2的空间,然后再重新找一块更大的空间,然后返回这块空间的地址.
}

malloc/calloc/realloc的区别?

在这里插入图片描述
在这里插入图片描述
3. 使用场景
malloc:适用于需要动态分配内存,但不需要初始化内存内容的场景。
calloc:适用于需要动态分配内存,并且希望内存内容初始化为0的场景。calloc的初始化特性使得它在分配内存后无需再进行额外的初始化操作。
realloc:适用于已分配的内存块大小不足以满足当前需求,或者分配了过大的内存块需要缩减的场景。realloc能够调整内存块的大小,同时尽可能保留原内存块中的数据。
4. 注意事项
使用malloc、calloc和realloc分配的内存都必须通过free函数来释放,以避免内存泄漏。
realloc在调整内存块大小时,如果成功则返回新的内存块地址(可能与原地址相同),如果失败则返回NULL,但原内存块仍然保持有效。因此,在使用realloc时,通常需要用一个临时指针来接收realloc的返回值,并在检查返回值后再决定是否更新原指针。
calloc分配的内存块会被初始化为0,这可能会带来一些额外的性能开销。如果不需要初始化内存内容,使用malloc可能更加高效。

三. C++中动态内存管理

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

对于内置类型

int main()
{
	//对于内置类型,除了用法方便,和malloc没什么区别
	int* p1 = new int;
	int* p2 = new int[10];
	//默认不初始化, 但是可以初始化

	int* p3 = new int(10);//初始化为10
	int* p4 = new int[10] {1, 2, 3, 4};

	delete p1;
	delete[] p2;
	return 0;
}

在这里插入图片描述

对于自定义类型

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

	A(int a1, int a2)
	{
		cout << "A(int a1, int a2)" << endl;
	}

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

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}

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

int main()
{
	//对于自定义类型, new能够调用构造初始化,malloc不在适用
	//A* p1 = new A;
	//A* p2 = new A(2);//传参构造

	//delete p1;//会调用析构函数
	//delete p2;

	A* p1 = new A[10];//连续申请10个空间
	//会调用十次默认构造
	//不想调用默认构造,下面是拷贝构造
	A aa1(1);
	A aa2(2);
	A aa3(3);
	A* p2 = new A[10]{ aa1,aa2,aa3 };

	A* p3 = new A[10]{ 1,2,3,4,{6,7} };//也可以直接写,进行隐式类型转化
	
	delete p1;
	delete p2;
	delete p3;
}

这里使用new进行内存开辟, 会自动调用它的构造函数, 使用delete也会自动调用它的析构函数

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

 //结论:不要错配使用,一定匹配使用,否则结果是不确定
int main()
{
	//对于自定义类型,delete[]需要保存需要析构的次数
	// 所以会多开辟四个字节进行存储析构的次数
	// 然后释放空间的时候会-4释放掉多开辟的空间
	// 而delete和free不会,所以会报错 
	//A* p1 = new A;
	A* p2 = new A[10];  //44 or 40
	delete[] p2;

	delete p2;
	free(p2);

	//对于内置类型无影响
	int* p3 = new int[10];  //40
	//free(p3);

	return 0;
}

在这里插入图片描述

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

五. new和delete的实现原理

1.内置类型

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

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

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

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

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

举个例子:

不通过new进行申请, 使用operator new, 跟malloc的用法一致, 只是前者会抛异常, 后者返回NULL, 那我们需要手动调用构造函数, 但是p1->A()这样写编译器不支持, 所以就需要用到定位new, 用法new(p1)A, 定位某个地址,进行调用其构造函数, 对于析构函数, 可以直接p1->~A()调用.

int main()
{
	//A* p1 = new A;

	//不想通过new进行申请,使用operator new需要手动调用构造函数
	A* p1 = (A*)operator new(sizeof(A));
	//p1->A(); // 编译器不支持这样显示调用构造
	//new(p1)A;  // 对已有空间,显示调用构造
	new(p1)A(10);  // 对已有空间,显示调用构造

	//后面回学到的内存池调用
	//A* p1 = pool.alloc(sizeof(A));
	//new(p1)A(10);  // 对已有空间,显示调用构造
	
	// delete p1
	p1->~A();//析构编译器支持
	operator delete(p1);
	
	// new []
	A* p2 = (A*)operator new[](sizeof(A)*10);
	//new(p2)A[10]{1,2,3,4};  // 对已有空间,显示调用构造
	for (int i = 0; i < 10; ++i)
		new(p2 + i)A(i);

	// delete[]
	for (int i = 0; i < 10; i++)
	{
		(p2 + i)->~A();
	}
	operator delete[](p2);

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

总结

本文深入探讨了C/C++内存管理机制,从内存分布、动态内存管理方式、new和delete的实现原理到定位new表达式,以及malloc/free和new/delete的区别,全面解析了C/C++内存管理的方方面面。

理解C/C++内存管理机制对于编写高效、安全的代码至关重要。掌握本文所述内容,能够帮助你更好地理解程序运行时的内存分配和释放过程,避免内存泄漏、越界访问等问题,进而提升代码质量。

希望本文能够对你有所帮助,如果你有任何疑问,欢迎在评论区留言讨论。


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

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

相关文章

jwt复习及ctfshow做题

Jwt复习总结 JWT简介 JWT即Json Web Token的缩写&#xff0c;顾名思义&#xff0c;是Token的一种。它常被用来在向服务器发起请求时用作身份认证。使用JWT作为身份认证的优势在于&#xff1a;它不需要在服务端去保留用户的认证信息。仅需要对该Token正确性进行校验即可,这就意…

张量分解(4)——SVD奇异值分解

&#x1f345; 写在前面 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;这里是hyk写算法了吗&#xff0c;一枚致力于学习算法和人工智能领域的小菜鸟。 &#x1f50e;个人主页&#xff1a;主页链接&#xff08;欢迎各位大佬光临指导&#xff09; ⭐️近…

那一单,教会我的不只是后悔

在销售行业中&#xff0c;每一笔成交都是一场精心策划的战役。每一个细节、每一次沟通都需要谨慎处理&#xff0c;以防辛苦建立的信任大厦瞬间崩塌。然而&#xff0c;即便是最小心谨慎的人也难免会犯错。我的一位好友&#xff0c;我们姑且称他为杰克&#xff0c;在一次不经意间…

实战篇(九):解锁3D魔方的秘密:用Processing编程实现交互式魔方

解锁3D魔方的秘密:用Processing编程实现交互式魔方 使用 Processing 创建一个 3D 魔方效果展示1. 安装 Processing2. 项目结构3. 代码实现4. 代码解释4.1. 初始化魔方4.2. 绘制魔方4.3. 处理鼠标事件4.4. 检查点击的面4.5. 旋转面和最终确定旋转5. 运行和测试6. 细节解释6.1. …

数据精度丢失

js数据精度丢失 最近看面试题想到了之前在开发钟遇到过的问题&#xff0c;现总结一下 在开发过程中&#xff0c;发现从后台返回的数据结构中的id字段在前端显示为不正确的值。经过排查&#xff0c;怀疑是JavaScript中Number类型精度丢失的问题。通过将id字段的类型从Number改为…

I/O多路复用:解锁服务器高性能的钥匙

文章目录 I/O 多路复用引言I/O 多路复用&#xff1a;并发处理的艺术介绍I/O多路复用的意义Linux下的I/O多路复用机制总结 文件描述符&#xff08;fd&#xff09;&#xff1a;连接的桥梁概述文件描述符的作用文件描述符的生命周期特殊的文件描述符文件描述符与I/O多路复用 套接字…

数据结构—链式二叉树-C语言

代码位置&#xff1a;test-c-2024: 对C语言习题代码的练习 (gitee.com) 一、前言&#xff1a; 在现实中搜索二叉树为常用的二叉树之一&#xff0c;今天我们就要通过链表来实现搜索二叉树。实现的操作有&#xff1a;建二叉树、前序遍历、中序遍历、后序遍历、求树的节点个数、求…

路径规划 | 基于DQN深度强化学习算法的路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 DQN路径规划算法 基于深度强化学习算法的路径规划 matlab2023b 栅格环境&#xff0c;走迷宫&#xff0c;可以通过窗口界面方便观察交互过程&#xff0c;代码注释详尽。 程序设计 完整源码和数据私信博主回复基于DQN深…

什么是大数据信用?它的作用有哪些?怎么查询大数据?

在金融行业中&#xff0c;风险管理是至关重要的一环。传统的信用评估方法主要基于借款人的财务状况和信用历史&#xff0c;但这些信息往往无法全面反映借款人的信用状况。大数据信用的出现为金融风控提供了新的解决方案。 首先&#xff0c;大数据信用可以为金融机构提供更全面的…

flutter实现语言的国际化

目录 前言 一、GetX实现国际化(推荐) 1.安装Getx 2.创建国际化的文件 3.使用国际化字符串 4.配置GetMaterialApp 5.更改语言 6.系统语言 ​编辑 7.原生工程配置 1.iOS工程配 1.打开iOS工程&#xff0c;在Project的info里面添加语言 2.创建String File文件 2.andr…

运维管理数智化:数据与智能运维场景实践

本文来自腾讯蓝鲸智云社区用户&#xff1a;CanWay 摘要&#xff1a;笔者根据自身的技术和行业理解&#xff0c;分享嘉为蓝鲸数据与智能运维场景实践。 涉及关键字&#xff1a;一体化运维、平台化运维、数智化运维、AIOps、运维PaaS、运维工具系统、蓝鲸等。 本文作者&#xf…

vue2迁移到vue3注意点

vue2迁移到vue3注意点 1、插槽的修改 使用 #default &#xff0c; 以及加上template 模板 2、 类型的定义&#xff0c;以及路由&#xff0c;vue相关资源&#xff08;ref, reactive,watch&#xff09;的引入等 3、类装饰器 1&#xff09;vue-class-component是vue官方库,作…

ActiveMQ-CVE-2023-46604

Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞 OpenWire协议在ActiveMQ中被用于多语言客户端与服务端通信。在Apache ActvieMQ5.18.2版本以及以前&#xff0c;OpenWire协议通信过程中存在一处反序列化漏洞&#xff0c;该漏洞可以允许具有网络访问权限的远程攻击者通过操作…

网页数据抓取:融合BeautifulSoup和Scrapy的高级爬虫技术

网页数据抓取&#xff1a;融合BeautifulSoup和Scrapy的高级爬虫技术 在当今的大数据时代&#xff0c;网络爬虫技术已经成为获取信息的重要手段之一。Python凭借其强大的库支持&#xff0c;成为了进行网页数据抓取的首选语言。在众多的爬虫库中&#xff0c;BeautifulSoup和Scrap…

1140 分珠

这是一个图的划分问题&#xff0c;可以使用深度优先搜索&#xff08;DFS&#xff09;或广度优先搜索&#xff08;BFS&#xff09;来解决。我们需要找到一种划分方式&#xff0c;使得划分后的两部分总重的差值的绝对值最小。 以下是对应的C代码&#xff1a; #include <iost…

房地产市场2024年展望——深度解读行业趋势

作为一名有十多年经验的地产营销人&#xff0c;对于2024年房地产行业的发展趋势&#xff0c;我认为可以从以下几个方面来探讨&#xff0c;如果觉得对你有帮助&#xff0c;请不吝一个三连&#xff08;赞同喜欢收藏&#xff09; 一、市场调整与分化加剧 在经历了较长时间的市场…

用chatgpt写了个二级导航,我全程一个代码没写,都是复制粘贴

今天心血来潮&#xff0c;让chatgpt给我写个移动端的二级导航菜单&#xff0c;效果如下&#xff1a; 1、两级导航&#xff0c;竖向排列&#xff0c;一级导航默认显示&#xff0c;二级隐藏 2、抽屉伸缩效果&#xff0c;点击一级导航&#xff0c;展开二级导航&#xff0c;再次点…

同步IO、异步IO以及五种网络IO模式

目录 一、同步IO和异步IO 二、五种网络IO模式 1、阻塞IO 2、非阻塞IO 3、IO多路复用 3.1、SELECT 3.2、POLL 3.3、EPOLL 一、同步IO和异步IO 场景1&#xff1a; 小明去打开水&#xff0c;而开水塔此时没有水&#xff0c;小明在现场一直等待开水到来&#xff0c;或者不断…

老牌数据库HRS,三分之二二区以上!| CHARLS等七大老年公共数据库周报(7.10)...

七大老年公共数据库 七大老年公共数据库共涵盖33个国家的数据&#xff0c;包括&#xff1a;美国健康与退休研究 (Health and Retirement Study, HRS)&#xff1b;英国老龄化纵向研究 &#xff08;English Longitudinal Study of Ageing, ELSA&#xff09;&#xff1b;欧洲健康、…

图片转文字的软件,分享3种不同的类型的软件!

在信息爆炸的时代&#xff0c;图片作为一种直观、生动的信息载体&#xff0c;已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;有时候我们可能需要将图片中的文字提取出来&#xff0c;以便于编辑、整理或进一步使用。那么&#xff0c;有哪些实用的图片转文字软件可以…