1024程序员节特辑 | 深度解析C/C++内存管理(建议收藏!!)

news2024/11/17 11:34:33

1024程序员节特辑 | 深度解析C/C++内存管理(建议收藏!!)

  • 一、C/C++内存分布
    • 1.1 相关例题
  • 二、 C语言中动态内存管理方式:malloc/calloc/realloc/free
    • 2.1 相关面试题
  • 三、C++内存管理方式
    • 3.1 new/delete操作内置类型
    • 3.2 new和delete操作自定义类型
  • 四、operator new与operator delete函数
    • 4.1 operator new抛异常演示
  • 五、new和delete的实现原理
    • 5.1 内置类型
    • 5.2 自定义类型
    • 5.3 相关例题
  • 六、定位new表达式(placement-new)
  • 七、7. 常见面试题
  • 7.1 malloc/free和new/delete的区别
    • 7.2 内存泄漏
      • 7.2.1 什么是内存泄漏,内存泄漏的危害
      • 7.2.2 内存泄漏分类
      • 7.2.3 如何检测内存泄漏
    • 7.2.4如何避免内存泄漏



一、C/C++内存分布

C/C++内存分布大致如下:

区域名称用途
栈(堆栈)存储非静态局部变量/函数参数/返回值等等,栈是向下增长的
内存映射段高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
于程序运行时动态内存分配,堆是可以上增长的
数据段存储全局数据和静态数据
代码段可执行的代码/只读常量

1.1 相关例题

代码如下:

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在哪里?____
    答案:C、C、C、A、A ;
    A、A、A、D、A、B
  1. sizeof 和 strlen 区别?
    sizeof是一个运算符,用于获取对象或类型的大小(以字节为单位)。它可以用于任何类型的变量,包括字符串。
    strlen是一个函数,用于获取以null字符(‘\0’)结尾的字符串的长度(以字符为单位)。它只能用于字符串,不能用于其他类型的变量。

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

2.1 相关面试题

【面试题】:

  1. malloc/calloc/realloc的区别?
    解析:malloc用于分配指定大小的内存块;calloc用于分配指定数量和大小的连续内存块并初始化为0;realloc用于重新分配已经分配的内存块的大小。

2.malloc的实现原理?
解析:glibc中malloc实现原理


三、C++内存管理方式

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

3.1 new/delete操作内置类型

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

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

	// 动态申请10个int类型的空间
	int* ptr6 = new int[3];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

图解:
在这里插入图片描述
小tips:

  1. 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。
  2. 相对于malloc,new不需要强转,并且会自动计算大小,。除此之外,还额外支持初始化!!

3.2 new和delete操作自定义类型

下面给出new和delete自定义类型的几种常见用法:

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

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

private:
	int _a;
};

int main()
{
	//malloc没有办法很好的支持自定义类型动态申请空间的初始化-->new
	A* p1 = (A*)malloc(sizeof(A));
	//p1->A(1); 创建对象时,自动调用,不可显示调用

	//new = malloc + 构造函数 (申请空间+ 初始化)
	A* p2 = new A;
	A* p3 = new A(1);
	//delete = 析构函数 + free
	delete p2;
	delete p3;

	A* p4 = new A[10];
	delete[] p4;

	A a1(1);
	A a2(2);
	A* p5 = new A[10]{ a1,a2 };
	delete[] p5;

	//匿名对象
	A* p6 = new A[10]{ A(1), A(2) };
	delete[] p6;

	//隐式类型转换
	A* p7 = new A[10]{ 1,2 };
	delete[] p7;
	return 0;
}

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


四、operator new与operator delete函数

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

operator new与operator delete本质上是malloc和free的封装,只不过operator 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来释放空间的。

4.1 operator new抛异常演示

下面通过开足够大的空间,同时通过try…catch来捕捉异常

void func()
{
	char* ptr = new char[0x7fffffff];
	cout << (void*)ptr << endl;
}

int main()
{
	try
	{
		//char* ptr = new char[0x7fffffff];
		//*cout << ptr << endl;*//对于char*, cout不会按指针打印,会按照字符串打印。
								//同时由于编码等问题,会报屯屯...
		//cout << (void*)ptr << endl;

		func();
		cout << "hello C++" << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

捕捉异样结果:
在这里插入图片描述


五、new和delete的实现原理

5.1 内置类型

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

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

5.3 相关例题

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

private:
	int _a;
};
int main()
{
	A* p2 = new A[10];
	delete p2;
	return 0;
}
int main()
{
	//Stack* ptr = (Stack*)operator new(sizeof(Stack));
	//operator delete(ptr);

	A* p2 = new A[10];
	//delete[] p2;
	//free(p2);
	delete p2;
	return 0;
}

Tips:没有显示的写析构函数是成员正常运行,显示写后会报错。
解析:


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

七、7. 常见面试题

7.1 malloc/free和new/delete的区别


malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
5. . malloc和free是函数,new和delete是操作符。
6. malloc申请的空间不会初始化;new可以初始化。
7. malloc申请空间时,需要手动计算空间大小并传递;new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
8. malloc的返回值为void*, 在使用时必须强转;new不需要,因为new后跟的是空间的类型。
9. malloc申请空间失败时,返回的是NULL,因此使用时必须判空;new不需要,但是new需要捕获异常。
10. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

7.2 内存泄漏

7.2.1 什么是内存泄漏,内存泄漏的危害


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

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

7.2.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  1. 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
    块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
    内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  2. 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
    掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

7.2.3 如何检测内存泄漏

在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks()函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息

int main()
{
	 int* p = new int[10];
	 // 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
	 _CrtDumpMemoryLeaks();
	 return 0;
}

	// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
	Detected memory leaks!
	Dumping objects ->
	{79} normal block at 0x00EC5FB8, 40 bytes long.
	Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
	Object dump complete.

因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。

  • 在linux下内存泄漏检测: Linux下几款C++程序中的内存泄露检查工具
  • 在windows下使用第三方工具: VLD工具说明
  • 其他工具: 内存泄漏工具比较

7.2.4如何避免内存泄漏

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

总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

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

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

相关文章

通过字符设备驱动的分步实现编写LED驱动,另外实现特备文件和设备的绑定

头文件.h文件 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; } gpio_t; #define PHY_LED1_ADDR 0X50006000 #define PHY_LED2_ADDR 0X5000700…

程序员网上接单盛行,到底该怎样选择一个好用不坑的接单平台?

现在&#xff0c;选择在网上接单的程序员是越来越多了&#xff0c;与此同时&#xff0c;网上接单的平台也是越来越多了&#xff0c;五花八门的平台&#xff0c;哪个最靠谱&#xff1f;哪个资源丰富一些&#xff1f; 这些问题是不是也让你犹豫了呢&#xff1f;不用担心&#xf…

红队打靶:Misdirection打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现与端口扫描 第二步&#xff1a;Web渗透&#xff08;80端口&#xff0c;战术放弃&#xff09; 第三步&#xff1a;Web渗透&#xff08;8080端口&#xff09; 第四步&#xff1a;sudo bash提权 第五步&#xff1a;/etc/passwd利…

Qt程序的发布和打包,任何电脑都可以安装

## 1. Qt程序的发布 当Qt程序编写完成通过IDE编译就可以得到对应的可执行程序,这个可执行程序在本地运行是完全没有问题的(因为在本地有Qt环境,程序运行过程中可以加载到相关的动态库),但是如果我们想把这个Qt程序给到其他小伙伴使用可能就会出问题了,原因如下: 对方电…

Leetcode刷题详解——二分查找

1. 题目链接&#xff1a;704. 二分查找 2. 题目描述&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1…

【线程本地变量ThreadLocal】—— 每天一点小知识

&#x1f4a7; 线程本地变量 T h r e a d L o c a l \color{#FF1493}{线程本地变量ThreadLocal} 线程本地变量ThreadLocal&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433;…

ES挂载不上怎么处理?

全文搜索 EelasticSearch安装 Docker安装 docker run -d --name es7 -e ES_JAVA_POTS"-Xms256m -Xmx256m" -e "discovery.typesingle-node" -v /home/206/es7/data/:/usr/share/elasticsearch/data -p 9200:9200 -p 9300:9300 elasticsearch:7.14.0 …

CANoe-使用IG Ethernet Packet Builder实现IP包分片的若干问题

在文章《CANoe-Ethernet IG和Ethernet Packet Builder的使用和区别》中,我们讲过Packet Builder可以组装多种类型的以太网报文: 当我们想组装一条icmpv4 echo request报文,payload只有1个字节的数据FF时,选择ICMPv4 Packet,创建一条ICMPv4报文,把payload改为1个字节: 然…

【类和对象之构造方法】

文章目录 用构造方法初始化对象格式&#xff1a;public类名特性 快捷键生成构造方法访问修饰限定符总结 用构造方法初始化对象 格式&#xff1a;public类名 注意区分成员方法和构造方法 两者都是在类当中但是成员方法的格式是public返回值方法名参数成员方法有参数&#xff…

Java中整数基础知识

原文链接 Java中整数基础知识 最近做了一道题&#xff0c;非常有意思&#xff0c;题本身很简单&#xff0c;但涉及到整数的最大值以及最小值&#xff0c;当写测试用例的时候&#xff0c;却犯了一个错误&#xff0c;发现最小整数并不是0xFFFFFFFF&#xff0c;我们来仔细看一下。…

网络协议--UDP:用户数据报协议

11.1 引言 UDP是一个简单的面向数据报的运输层协议&#xff1a;进程的每个输出操作都正好产生一个UDP数据报&#xff0c;并组装成一份待发送的IP数据报。这与面向流字符的协议不同&#xff0c;如TCP&#xff0c;应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联…

Citrix XenDesktop云桌面单点登录XenApp虚拟应用小技巧

哈喽大家好,欢迎来到虚拟化时代君(XNHCYL)。 “ 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利……(每天更新不间断,福利不见不散)” 第1章 前言 实现XenDesktop的桌面打开XenApp发布的应用…

为什么我们从github clone下来的maven项目本地运行报错

github上的项目clone到本地&#xff0c;比如是个Springboot的项目&#xff0c;我们用idea运行莫名其妙的报各种问题&#xff0c;常见的有以下异常&#xff1a; java.lang.NoClassDefFoundError&#xff1a;xxxxjava.lang.ClassNotFoundException:xxxxxjava.lang.NoSuchMethodE…

64 最长公共子序列

最长公共子序列 题解1 DP 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的 最长公共子序列的长度。如果不存在 公共子序列&#xff0c;返回 0 。 一个字符串的子序列是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些…

【CCF】Z字形扫描

这题的关键是将整个扫描的过程&#xff0c;拆分成很多次斜着操作数组的过程。 而且这个过程中可以建立如下规律&#xff1a; &#xff08;1&#xff09;一斜线上的元素个数与切换到下一条斜线这一操作之间建立规律。 先讨论左上部分的数组&#xff1a; 1&#xff09;当元素个…

本地部署Stackedit Markdown编辑器并通过cpolar内网穿透实现远程访问

文章目录 1. docker部署Stackedit2. 本地访问3. Linux 安装cpolar4. 配置Stackedit公网访问地址5. 公网远程访问Stackedit6. 固定Stackedit公网地址 StackEdit是一个受欢迎的Markdown编辑器&#xff0c;在GitHub上拥有20.7k Star&#xff01;&#xff0c;它支持将Markdown笔记保…

指针-Pointer

0.1 地址 &#xff08;1&#xff09;字节&#xff08;Byte) 一个字节存储8位无符号数&#xff08;1Byte 8 bit)&#xff0c;每个字节储存的数值范围为0-255 &#xff08;2&#xff09;内存 1G 1024M 、1M 1024K 、 1K 1024Byte 、1Byte 8 bit &#xff08;3&#xff…

谈谈你对spring boot 3.0的理解

谈谈你对spring boot 3.0的理解 一&#xff0c;Spring Boot 3.0 的兼容性 Spring Boot 3.0 在兼容性方面做出了很大的努力&#xff0c;以支持存量项目和老项目。尽管如此&#xff0c;仍需注意以下几点&#xff1a; Java 版本要求&#xff1a;Spring Boot 3.0 要求使用 Java 1…

高等数学啃书汇总重难点(五)定积分

最近都在忙着刷题&#xff0c;尤其是政治和英语也开始加量复习了&#xff0c;该系列断更了将近2个月~不过最近在刷题的时候又遇到一些瓶颈&#xff0c;因此回归基础来整理一下知史点~ 总的来说&#xff0c;虽然第五章也是重中之重&#xff0c;定理数量也很多&#xff0c;但&…

通过条件竞争实现内核提权

条件竞争漏洞&#xff08;Race Condition Vulnerability&#xff09;是一种在多线程或多进程并发执行时可能导致不正确行为或数据损坏的安全问题。这种漏洞通常发生在多个线程或进程试图访问和修改共享资源&#xff08;如内存、文件、网络连接等&#xff09;时&#xff0c;由于…